前言
本文属于笔者Vue3源码阅读系列第一篇文章
响应式是什么
在阅读源码之前,我们先来了解一下什么是响应式 用大白话讲就是,数据改变,状态也自动保持同步。 举个例子:
javascript
const obj = {text:'hello world'}
// 一个副作用函数
functione effect(){
// 读取obj.text
document.bdoy.innnerText = obj.text
}
上面这段代码,如果我们修改了obj.text
的值,effec
函数自动执行,那么ob就可以称之为响应式数据。
Vue2的响应式
上图来自 官方文档,Vue初始化时对状态数据通过Obejct.defineProperty
方法进行劫持,在执行组件render时会读取这些数据,触发数据的getter
,然后组件的 watcher实例会把这些数据状态收集为依赖,当数据状态变更触发setter
,setter
通知watcher,然后render 函数重新执行更新组件。这样就完成了响应式的过程。 Object.defineProperty
的特点如下。
Object.defineProperty
是通过给对象属性进行数据劫持的,需要对每个对象,每个属性进行遍历,如果是嵌套对象,还需要深度遍历。性能问题会比较明显。- 无法劫持属性的添加、删除操作,只能劫持已有属性的变化。
- 兼容性比较好吧。
阅读Vue3 Reactivity源码
Vue3
使用Proxy
来实现响应式,因为它可以对整个对象进行代理,包括对象的所有属性、数组的所有元素以及类数组对象的所有元素**,**支持13种拦截操作。
reactive
接下来看看reactive的源码 (packages/reactivity/src/reactive.ts) reactive
方法的逻辑,先判断传入的对象是不是只读,如果是直接返回,否则调用createReactiveObject
。
createReactiveObject
createReactiveObject
主要是做一些校验。
- 判断target 是不是 object,不是直接返回。
- 判断target是不是已经是Proxy,直接返回。
- 对一个原始对象多次响应式处理,直接返回。
- 不能被代理的情况。
- 最后返回
new Proxy(...)
下面看一下mutableHandlers
的实现
BaseHandlers
mutableHandlers
实现在packages\reactivity\src\baseHandlers.ts
,在baseHandlers
中包含四种handler
- mutableHandlers 可变处理。
- readonlyHandlers 只读处理。
- shallowReactiveHandlers 浅响应式处理。
- shallowReadonlyHandlers 浅响应和只读处理。
我们重点关注mutableHandlers
的实现
deleteProperty
用于拦截从target删除某个属性的操作(delete obj.xxx), 先检查target 中是否有这个key,然后获取这个属性的值,然后调用Reflect.deleteProperty
删除key,如果删除成功的话,就调用tigger
触发更新。
has
用于拦截判断某个key 是否存在于target中,先调用Reflect.has
来拿到结果,然后判断是不是Symbol,如果不是,或者也不在builtInSymbols
中,就调用track
收集依赖。
ownKeys
用于拦截遍历操作,先调用track
收集依赖,然后调用Reflect.ownkeys
返回结果。
get
上图逻辑,处理这个四个标识,该分别返回什么值。
上图逻辑,判断target是不是Array,如果是,判断key是不是'includes', 'indexOf', 'lastIndexOf','push', 'pop', 'shift', 'unshift', 'splice' 中的一种,如果是就返回arrayInstrumentations
中对应的方法。 我们看看重写includes, indexOf, lastIndexOf干了些什么:
- 先调用toRaw 获取原始数组
- 遍历每一项,对每一项都
track
依赖收集。 - 调用数组的'includes', 'indexOf', 'lastIndexOf'得到结果res
- 如果是-1 或者是false,将参数调用toRow得到原始对象后再次调用数组的'includes', 'indexOf', 'lastIndexOf'并返回结果
- 否则返回第三步的结果。
我们看看重写push, pop, shift, unshift, splice干了些什么:
- 暂停track依赖收集
- 调用数组API 得到结果
- 恢复track
- 返回结果
然后看看get后面的逻辑 调用Reflect.get 获取本次get的结果res 如果key 是Symbol,并且在builtInSymbols,就返回res ,或者不是Symbol,但是在isNonTrackableKeys 也返回res 如果不是只读,就track 依赖收集 如果是浅响应,就返回res 如果res 是Ref,并且target是数组,并且key 是正整数,就返回res,否则返回res.value 如果res是对象类型,就接着进行响应式处理,并返回代理对象,根据isReadonly
调用readonly/reactive
, 嵌套对象的响应式是在get 才会响应式处理 如果上面的都没命中就会返回res.
set
- 先获取到当前值的oldValue
- 如果不是浅响应,新值(value)不是浅响应并且也不是只读的就通过
toRaw
获得就值和新值的原始数据 - 如果target 不是数组,并且oldValue是Ref并且,value不是Ref,但是oldValue是只读的,就返回false,否则oldValue.value 的值设置为新值(value),并返回true.
- 如果target是数组,并且key 是整数的话就判断key 是不是小于数组的length,否则就调用hasOwn判断是否key 是否在 target上
- 调用Reflect.set,设置value
- 如果target是原始原型链中的某个内容,则不触发
- 如果hadKey是false,就调用trigger 触发更新
- 如果value和oldValue 相等,不触发更新
总结
到此,我们已经读完了响应式reactive的内容,来总结一下
- 响应式的概念
- vue2 响应式的实现
- reactive 的源码
- createReactiveObject的源码
- baseHandlers的源码
如果本文对你有一点帮助,请点一个大大的赞,你的支持就是我创作的动力。