Vue3中非响应式变量表现异常的原因探究
问题场景
我定义了两个变量:reactiveVariable(用ref声明的响应式变量)和noneReactiveVariable(普通非响应式变量),遇到三个疑问:
- 初始代码中点击按钮,模板里两个变量都变了,但
noneReactiveVariable没做响应式声明,为啥会更新? - 若模板只保留
noneReactiveVariable的展示,点击按钮后它不再更新,但nextTick仍触发,既然模板更新了,为啥不渲染新值? - 更新代码后,
noneReactiveVariable在定时器中定时修改,点击按钮时它仍会同步到DOM,原因是什么?
代码示例
初始代码
<template> <input type="button" value="click" @click="clickBtn"/> <div id="reactiveDiv">{{ reactiveVariable }}</div> <div id="noneReactiveDiv">{{ noneReactiveVariable }}</div> </template> <script setup lang="ts"> import { ref, nextTick } from "vue" const reactiveVariable = ref('reactive') let noneReactiveVariable = 'non reactive ' function clickBtn(){ reactiveVariable.value = (new Date().getTime()).toString() noneReactiveVariable = (new Date().getTime()).toString() nextTick(()=>{ console.log('next tick') }) } </script>
简化模板后的代码
<template> <input type="button" value="click" @click="clickBtn"/> <div id="noneReactiveDiv">{{ noneReactiveVariable }}</div> </template> <script setup lang="ts"> import { ref, nextTick } from "vue" const reactiveVariable = ref('reactive') let noneReactiveVariable = 'non reactive ' function clickBtn(){ reactiveVariable.value = (new Date().getTime()).toString() noneReactiveVariable = (new Date().getTime()).toString() nextTick(()=>{ console.log('next tick') }) } </script>
更新后的代码
<template> <input type="button" value="click" @click="clickBtn"/> <div id="reactiveDiv">{{ reactiveVariable }}</div> <div id="noneReactiveDiv">{{ noneReactiveVariable }}</div> </template> <script setup lang="ts"> import { ref, nextTick, onMounted } from "vue" const reactiveVariable = ref('reactive') let noneReactiveVariable = 'non reactive ' onMounted(()=>{ setInterval(()=>{ noneReactiveVariable = (new Date().getTime()).toString() },1000) }) function clickBtn(){ reactiveVariable.value = (new Date().getTime()).toString() nextTick(()=>{ console.log('next tick') }) } </script>
疑问解答
1. 初始代码中非响应式变量为啥跟着更新?
你猜的没错:响应式变量的变更触发了组件整体重新渲染。
Vue的响应式机制是:当响应式数据(比如reactiveVariable.value)变更时,会标记当前组件为需要更新,随后触发组件的重新渲染流程。在重新渲染时,Vue会重新执行模板渲染函数,读取所有模板中引用变量的当前内存值——包括非响应式的noneReactiveVariable。因为你在修改响应式变量的同时,已经把noneReactiveVariable的内存值改成了新的时间戳,所以重新渲染时自然会把新值渲染到DOM里。
要明确的是:这只是组件重新渲染带来的“顺带效果”,noneReactiveVariable本身没有响应式能力,如果单独修改它,完全不会触发组件更新。
2. 简化模板后非响应式变量为啥不再更新?
当模板里只保留noneReactiveVariable时,Vue的编译优化会检测到:这个组件的渲染没有依赖任何响应式变量,于是会把组件编译成静态节点,只会在初始化时渲染一次,后续不会再重新渲染。
点击按钮时,你虽然修改了reactiveVariable.value,但这个变量已经不在模板里被引用了,所以Vue不会触发组件重新渲染。至于nextTick会触发,是因为修改响应式变量的操作本身会触发Vue的响应式更新流程(哪怕没有组件依赖它),这个流程会异步执行,nextTick会捕获到这个流程结束的时机,但实际上组件的DOM根本没变化。
3. 定时器修改非响应式变量,点击按钮为啥仍更新?
定时器一直在后台修改noneReactiveVariable的内存值,但因为它是非响应式的,所以不会主动触发组件更新。而点击按钮时,修改reactiveVariable.value触发了组件重新渲染,此时Vue会读取noneReactiveVariable的最新内存值(已经被定时器改了N次),并同步到DOM里。
本质还是响应式变量触发的组件重新渲染,顺带把非响应式变量的最新值渲染了出来。
内容的提问来源于stack exchange,提问作者James

