Vue3源码阅读-响应式 reactive

前言

本文属于笔者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实例会把这些数据状态收集为依赖,当数据状态变更触发settersetter通知watcher,然后render 函数重新执行更新组件。这样就完成了响应式的过程。 Object.defineProperty的特点如下。

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

阅读Vue3 Reactivity源码

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

reactive

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

createReactiveObject

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的实现

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干了些什么:

  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后面的逻辑 调用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

  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的源码

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

相关推荐
余道各努力,千里自同风2 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave9 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟11 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾33 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧41 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
chusheng18401 小时前
Java项目-基于SpringBoot+vue的租房网站设计与实现
java·vue.js·spring boot·租房·租房网站
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
游走于计算机中摆烂的1 小时前
启动前后端分离项目笔记
java·vue.js·笔记