Vue3响应式系统的核心设计
Vue3的响应式系统基于Proxy实现,替代了Vue2的Object.defineProperty。Proxy可以直接拦截对象的读取、写入、删除、枚举等操作,天然支持数组下标和动态新增属性,这是Vue2需要Vue.set的原因。响应式系统由三个角色组成:reactive/ref创建响应式对象,effect注册副作用函数,依赖收集与触发更新在get/set拦截中完成。
先看一个最小实现,理解响应式工作的链路:读取属性时把当前副作用函数登记到依赖表,修改属性时从依赖表取出函数重新执行。
import { reactive, effect } from 'vue'
const state = reactive({ count: 0 })
effect(() => { console.log(state.count) })
state.count++ // 触发副作用重新执行
ref与reactive的取舍与包装原理
reactive直接代理对象,对基本类型无效;ref则把任意值包进{value},适合单独声明基本类型的响应变量。ref在模板中自动解包,脚本里需要.value。两者混用时,ref放进reactive里自动解包,但要避免在解构时丢失响应性。
const count = ref(0)
const obj = reactive({ list: [] })
// toRefs保留解构后的响应性
const { list } = toRefs(obj)
reactive用Proxy包装,访问属性时触发track,赋值时触发trigger。深层响应通过递归调用reactive实现,浅层版本shallowReactive只代理首层,性能更好,适合列表这种结构。
effect副作用调度与缓存优化
effect是响应系统的执行引擎。它的scheduler调度选项决定了触发时是同步执行还是排队异步执行。Vue的组件渲染与computed都依赖这个机制:批量更新时把任务丢进微任务队列,避免多次渲染。
const runner = effect(
() => state.count + 1,
{
lazy: true, // 不立即执行
scheduler: (job) => queueJob(job) // 调度执行
}
)
runner() // 手动执行
computed缓存逻辑:依赖未变时不重新计算,直接返回缓存值;依赖变化后置为脏,再次访问才计算。这在昂贵计算场景价值明显,比如过滤大列表。
watch与watchEffect的触发时机控制
watch需要显式指定侦听的源,能拿到新旧值;watchEffect自动收集回调中访问的响应依赖,依赖变化就重新执行。watch适合异步场景,比如搜索防抖;watchEffect适合同步副作用,比如记录日志。
watch(source, (newVal, oldVal) => {
// 新旧值对比后处理
}, { flush: 'post' })
watchEffect(() => {
saveToStorage(state.list) // 自动追踪
})
flush选项控制回调时机:pre在组件渲染前,post在渲染后,sync每次变化立即执行。默认pre。组件卸载时记得停止watch,避免内存泄漏。
响应式性能:shallowRef与数据结构的取舍
大型列表用reactive逐层代理开销大,用shallowRef包裹引用类型,替换整个值时只触发一次更新。依赖图层级深、结构大的对象,可以改用shallow减少代理开销。
import { shallowRef } from 'vue'
const bigList = shallowRef([])
// 整体替换触发更新
bigList.value = newData
Object.freeze冻结的常量对象不需要响应式,跳过代理能省掉一批内存。写响应式代码时先想清楚数据形状,业务数据与临时变量分开,避免把本不该共享的状态放进全局reactive。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-xiang-ying-shi-yuan-li-yu-yi-lai-shou-ji-shi-zhan-ref/