Vue3源码阅读-响应式 reactive

前言

本文属于笔者Vue3源码阅读系列第一篇文章

响应式是什么

在阅读源码之前,我们先来了解一下什么是响应式
用大白话讲就是,数据改变,状态也自动保持同步
举个例子:

const obj = {text:'hello world'}
// 一个副作用函数
functione effect(){
  // 读取obj.text
  document.bdoy.innnerText = obj.text
}

上面这段代码,如果我们修改了obj.text的值,effec函数自动执行,那么ob就可以称之为响应式数据。

Vue2的响应式

Vue3源码阅读-响应式 reactive
上图来自 官方文档,Vue初始化时对状态数据通过Obejct.defineProperty方法进行劫持,在执行组件render时会读取这些数据,触发数据的getter,然后组件的 watcher实例会把这些数据状态收集为依赖,当数据状态变更触发settersetter通知watcher,然后render 函数重新执行更新组件。这样就完成了响应式的过程。
Object.defineProperty的特点如下。

  1. Object.defineProperty是通过给对象属性进行数据劫持的,需要对每个对象,每个属性进行遍历,如果是嵌套对象,还需要深度遍历。性能问题会比较明显。
  2. 无法劫持属性的添加、删除操作,只能劫持已有属性的变化。
  3. 兼容性比较好吧。

阅读Vue3 Reactivity源码

Vue3使用Proxy来实现响应式,因为它可以对整个对象进行代理,包括对象的所有属性、数组的所有元素以及类数组对象的所有元素**,**支持13种拦截操作

reactive

接下来看看reactive的源码 (packages/reactivity/src/reactive.ts)
Vue3源码阅读-响应式 reactive
reactive方法的逻辑,先判断传入的对象是不是只读,如果是直接返回,否则调用createReactiveObject

createReactiveObject

Vue3源码阅读-响应式 reactive
createReactiveObject 主要是做一些校验。

  1. 判断target 是不是 object,不是直接返回。
  2. 判断target是不是已经是Proxy,直接返回。
  3. 对一个原始对象多次响应式处理,直接返回。
  4. 不能被代理的情况。
  5. 最后返回new Proxy(...)

下面看一下mutableHandlers的实现

BaseHandlers

mutableHandlers实现在packages\reactivity\src\baseHandlers.ts,在baseHandlers中包含四种handler

  1. mutableHandlers 可变处理。
  2. readonlyHandlers 只读处理。
  3. shallowReactiveHandlers 浅响应式处理。
  4. shallowReadonlyHandlers 浅响应和只读处理。

我们重点关注mutableHandlers的实现
Vue3源码阅读-响应式 reactive

deleteProperty

Vue3源码阅读-响应式 reactive
用于拦截从target删除某个属性的操作(delete obj.xxx), 先检查target 中是否有这个key,然后获取这个属性的值,然后调用Reflect.deleteProperty删除key,如果删除成功的话,就调用tigger触发更新。

has

Vue3源码阅读-响应式 reactive
用于拦截判断某个key 是否存在于target中,先调用Reflect.has来拿到结果,然后判断是不是Symbol,如果不是,或者也不在builtInSymbols中,就调用track收集依赖。

ownKeys

Vue3源码阅读-响应式 reactive
用于拦截遍历操作,先调用track收集依赖,然后调用Reflect.ownkeys返回结果。

get

Vue3源码阅读-响应式 reactive
上图逻辑,处理这个四个标识,该分别返回什么值。
Vue3源码阅读-响应式 reactive

上图逻辑,判断target是不是Array,如果是,判断key是不是’includes’, ‘indexOf’, ‘lastIndexOf’,’push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’ 中的一种,如果是就返回arrayInstrumentations中对应的方法。
Vue3源码阅读-响应式 reactive
我们看看重写includes, indexOf, lastIndexOf干了些什么:

  1. 先调用toRaw 获取原始数组
  2. 遍历每一项,对每一项都track依赖收集。
  3. 调用数组的’includes’, ‘indexOf’, ‘lastIndexOf’得到结果res
  4. 如果是-1 或者是false,将参数调用toRow得到原始对象后再次调用数组的’includes’, ‘indexOf’, ‘lastIndexOf’并返回结果
  5. 否则返回第三步的结果。

我们看看重写push, pop, shift, unshift, splice干了些什么:

  1. 暂停track依赖收集
  2. 调用数组API 得到结果
  3. 恢复track
  4. 返回结果

然后看看get后面的逻辑
Vue3源码阅读-响应式 reactive
调用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

Vue3源码阅读-响应式 reactive

  1. 先获取到当前值的oldValue
  2. 如果不是浅响应,新值(value)不是浅响应并且也不是只读的就通过toRaw获得就值和新值的原始数据
  3. 如果target 不是数组,并且oldValue是Ref并且,value不是Ref,但是oldValue是只读的,就返回false,否则oldValue.value 的值设置为新值(value),并返回true.
  4. 如果target是数组,并且key 是整数的话就判断key 是不是小于数组的length,否则就调用hasOwn判断是否key 是否在 target上
  5. 调用Reflect.set,设置value
  6. 如果target是原始原型链中的某个内容,则不触发
  7. 如果hadKey是false,就调用trigger 触发更新
  8. 如果value和oldValue 相等,不触发更新

总结

到此,我们已经读完了响应式reactive的内容,来总结一下

  1. 响应式的概念
  2. vue2 响应式的实现
  3. reactive 的源码
  4. createReactiveObject的源码
  5. baseHandlers的源码

如果本文对你有一点帮助,请点一个大大的赞,你的支持就是我创作的动力。

原文链接:https://juejin.cn/post/7345339165418029056 作者:喝水运动员

(0)
上一篇 2024年3月13日 上午10:16
下一篇 2024年3月13日 上午10:26

相关推荐

发表回复

登录后才能评论