业务背景
项目中我要根据路由的不同动态修改一个dom元素的样式,思路很简单直接,通过ref获取dom元素,watch监听路由变化,路由变化时修改dom元素的样式,于是有了如下:
错误代码
js:
// 针对底部assess-footer宽度问题进行敏捷修复
const assessFooter = ref<HTMLElement | null>(null);
watch(() => router.currentRoute.value.path, (newValue, oldValue) => {
if (newValue.startsWith('/assess/question') || newValue.startsWith('/assess/report/psyAssess')) { // 根据路由动态切换样式
assessFooter.value && (assessFooter.value.style.minWidth = '');
} else if (newValue.startsWith('/assess/report') || newValue.startsWith('/assess/oreport')) {
assessFooter.value && (assessFooter.value.style.minWidth = '1200px');
}
}, {
immediate: true
});
html:
<div
class="assess-footer"
ref="assessFooter"
>
<div>
<p>TIC智能测评中心</p>
<img :src="TICLogo" />
</div>
</div>
js部分没必要关注细节,总而言之watch回调函数的第一次执行中,assessFooter值为null,所以导致样式没改成,bug没修复...
分析
在执行setup函数时,由watch源码可知,如果设置了immediate: true,那么job函数(也就是watch的回调)会当作当前宏任务的一个同步代码去执行(多说一嘴,之后由于响应式变量变化而再次触发watch时,其回调函数就是被Promise.resolve().then包装的微任务了)
然后回忆一下利用ref获取dom结点的步骤,html模版中给dom元素添加ref属性,js中声明一个同名的ref变量并初始化为null,setup函数return这个ref变量,ref就绑定好这个dom元素了。(如果setup不return是不行的)
综上,开启了immediate属性的watch回调会在ref绑定dom之前执行(watch回调执行时setup还没return),所以开启了immediate: true的watch回调里使用通过ref.value访问dom只会获取null
正解
(使用nextTick延迟对ref变量的访问
// 针对底部assess-footer宽度问题进行敏捷修复
const assessFooter = ref<HTMLElement | null>(null);
watch(() => router.currentRoute.value.path, (newValue, oldValue) => {
if (newValue.startsWith('/assess/question') || newValue.startsWith('/assess/report/psyAssess')) { // 根据路由动态切换样式
nextTick(() => {
assessFooter.value && (assessFooter.value.style.minWidth = '');
});
} else if (newValue.startsWith('/assess/report') || newValue.startsWith('/assess/oreport')) {
nextTick(() => {
assessFooter.value && (assessFooter.value.style.minWidth = '1200px');
});
}
}, {
immediate: true
});