ref获取dom与watch的执行先后

417 阅读2分钟

业务背景

项目中我要根据路由的不同动态修改一个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变量并初始化为nullsetup函数return这个ref变量,ref就绑定好这个dom元素了。(如果setupreturn是不行的)

综上,开启了immediate属性的watch回调会在ref绑定dom之前执行(watch回调执行时setup还没return),所以开启了immediate: truewatch回调里使用通过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
});