Vue3初始渲染后prop值变化时组件重新渲染的问题
问题根源
你的思路方向是对的,但遗漏了watch默认不会触发初始值这个点——mounted里注册的watch,只会在endvalue发生变化时执行回调,组件第一次挂载时的初始prop值不会触发它,所以初始渲染没效果;去掉watch直接在mounted里处理,又没监听prop变化,自然无法响应更新。
修复方案
把进度更新的逻辑抽成独立方法,既在mounted里调用处理初始值,又通过watch监听prop变化时调用,同时要注意清除旧定时器,避免多个定时器冲突:
步骤1:提取复用方法
在methods里新增updateProgress方法,把原来watch里的逻辑移进去,同时修复原代码里的错误(比如误用this.newEndValue,应该用传入的目标值):
methods: { updateProgress(targetValue) { const progressBar = this.$refs.progressBar; const valueContainer = this.$refs.valueContainer; // 先清除之前的定时器,避免叠加 if (this.progressTimer) { clearInterval(this.progressTimer); } let progressValue = 0; const speed = 10; this.progressTimer = setInterval(() => { if (targetValue < 0) { valueContainer.innerText = '---'; clearInterval(this.progressTimer); return; } if (targetValue !== 0) { progressValue++; } valueContainer.innerText = `${progressValue}%`; if (progressValue >= 100) { valueContainer.style.color = 'green'; progressBar.style.background = 'green'; } else { valueContainer.style.color = 'black'; progressBar.style.background = `conic-gradient( #192A44 ${progressValue * 3.6}deg, #EEEEEE ${progressValue * 3.6}deg )`; } // 到达目标值或超过100时停止 if (progressValue >= targetValue || progressValue >= 100) { clearInterval(this.progressTimer); } }, speed); } }
步骤2:初始化与监听
在data里添加定时器变量,然后在mounted里调用初始值,同时注册watch:
data() { return { progressTimer: null // 存储定时器ID,用于清除 } }, mounted() { // 初始渲染时调用,传入初始prop值 this.updateProgress(this.endvalue); // 监听prop变化 this.$watch('endvalue', (newEndValue) => { this.updateProgress(newEndValue); }); }
额外优化:组件销毁时清除定时器
添加beforeUnmount钩子,避免组件销毁后定时器还在运行:
beforeUnmount() { if (this.progressTimer) { clearInterval(this.progressTimer); } }
修改后的完整代码
<template> <div class="progressDisplay"> <div class="progressBar" ref="progressBar"> <div class="coverCircle"> <div class="valueContainer" ref="valueContainer"></div> </div> </div> </div> </template> <script> export default { name: 'ProgressCircle', props: { endvalue: { type: Number, required: true } }, data() { return { progressTimer: null } }, methods: { updateProgress(targetValue) { const progressBar = this.$refs.progressBar; const valueContainer = this.$refs.valueContainer; if (this.progressTimer) { clearInterval(this.progressTimer); } let progressValue = 0; const speed = 10; this.progressTimer = setInterval(() => { if (targetValue < 0) { valueContainer.innerText = '---'; clearInterval(this.progressTimer); return; } if (targetValue !== 0) { progressValue++; } valueContainer.innerText = `${progressValue}%`; if (progressValue >= 100) { valueContainer.style.color = 'green'; progressBar.style.background = 'green'; } else { valueContainer.style.color = 'black'; progressBar.style.background = `conic-gradient( #192A44 ${progressValue * 3.6}deg, #EEEEEE ${progressValue * 3.6}deg )`; } if (progressValue >= targetValue || progressValue >= 100) { clearInterval(this.progressTimer); } }, speed); } }, mounted() { this.updateProgress(this.endvalue); this.$watch('endvalue', (newEndValue) => { this.updateProgress(newEndValue); }); }, beforeUnmount() { if (this.progressTimer) { clearInterval(this.progressTimer); } } } </script> <style scoped> .progressDisplay { display: flex; justify-content: center; align-self: center; height: 300px; width: 300px; } .progressBar { height: 250px; width: 250px; border-radius: 50%; background-color: #EEEEEE; display: grid; place-items: center; -webkit-box-shadow: inset -8px -2px 3px -1px rgba(0, 0, 0, 0.02); -moz-box-shadow: inset -8px -2px 3px -1px rgba(0,0,0,0.02); box-shadow: inset -8px -2px 3px -1px rgba(0, 0, 0, 0.068); } .coverCircle { height: 200px; width: 200px; display: grid; place-items: center; background-color: white; border-radius: 50%; -webkit-box-shadow: -8px -2px 43px -1px rgba(0,0,0,0.31); -moz-box-shadow: -8px -2px 43px -1px rgba(0,0,0,0.31); box-shadow: -8px -2px 2px rgba(0, 0, 0, 0.077); } .valueContainer { position: relative; font-size: 34px; font-weight: 500; } </style>
内容的提问来源于stack exchange,提问作者Jtrom2021
相关产品推荐
相关产品推荐

