实现Vue3中的响应式Api

2,352 阅读15分钟

Vue3响应式Api实现

1. 前言

Vue3中的响应式Api中大量使用了ES6语法,在读本文之前请先了解以下知识点

预备知识点简单介绍MDN链接
1. ProxyProxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等developer.mozilla.org/zh-CN/docs/…
2. ReflectReflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法developer.mozilla.org/zh-CN/docs/…
3. WeakMapWeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的,可以及时的被垃圾回收developer.mozilla.org/zh-CN/docs/…
4. Set值的集合,可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的developer.mozilla.org/zh-CN/docs/…
5. Map存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值developer.mozilla.org/zh-CN/docs/…
6.Object.defineProperty直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。developer.mozilla.org/zh-CN/docs/…

2. 介绍Vue3

2.1 vue3介绍

  • Vue3.0 在去年 9 月正式发布了,随着时间的变化,现在Vue3.0生态已经趋向于稳定了
  • Vue3采用Typescript开发,增强类型检测。 Vue2 则采用flow
  • Vue3的性能优化,支持tree-shaking, 不使用就不会被打包
  • Vue3劫持数据采用Proxy,Vue2劫持数据采用Object.defineProperty。 defineProperty有性能问题和缺陷
  • Vue3 采用compositionApi进行组织功能,解决反复横跳,优化复用逻辑 (mixin带来的数据来源不清晰、命名冲突等), 相比optionsApi 类型推断更加方便
  • 源码采用 monorepo 方式进行管理,将模块拆分到package目录中
  • 增加了 Fragment,TeleportSuspense组件

2.2 vue3架构分析

2.2.1 Monorepo介绍

  • Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)
  • 一个仓库可维护多个模块,不用到处找仓库
  • 方便版本管理和依赖管理,模块之间的引用,调用都非常方便

缺点: 仓库体积会变大

2.2.2 Vue3源码结构

目录说明
reactivity响应式系统
runtime-core与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
runtime-dom针对浏览器的运行时。包括DOM API,属性,事件处理等
runtime-test用于测试
server-renderer用于服务器端渲染
compiler-core与平台无关的编译器核心
compiler-dom针对浏览器的编译模块
compiler-ssr针对服务端渲染的编译模块
compiler-sfc针对单文件解析
size-check用来测试代码体积
template-explorer用于调试编译器输出的开发工具
shared多个包之间共享的内容
vue完整版本,包括运行时和编译器

3. 响应式Api介绍

本文不会过多介绍具体用法,具体介绍和使用及完整Api请参考官方文档,下方为我们要去实现的响应式Api及简单介绍

Api简单介绍文档链接
reactive返回响应式的代理对象,深度递归懒代理,当取值时才会进行深度代理vue3js.cn/docs/zh/api…
shallowReactive返回响应式的代理对象,浅层代理,只代理第一层对象vue3js.cn/docs/zh/api…
readonly返回响应式的代理对象,深度递归懒代理,代理的值不可被更改vue3js.cn/docs/zh/api…
shallowReadonly返回响应式的代理对象,浅层代理,代理的值不可被更改vue3js.cn/docs/zh/api…
ref接受一个内部值并返回一个响应式且可变的 ref 对象vue3js.cn/docs/zh/api…
shallowRef浅层代理内部值并返回一个响应式且可变的 ref 对象vue3js.cn/docs/zh/api…
toRef可以将 ref 传递出去,从而保持对其源 property 的响应式连接vue3js.cn/docs/zh/api…
toRefs可以将多个 ref 传递出去,从而保持对其源 property 的响应式连接vue3js.cn/docs/zh/api…
computed计算属性,用法类似Vue2,原理不同vue3js.cn/docs/zh/api…

4. 响应式Api实现

4.1 搭建开发环境

4.1.0 说明

为了能保证1比1的还原及可阅读性,下方在实现过程中使用了极少的typescript语法

4.1.1 安装依赖

依赖
typescript支持typescript
rollup打包工具
rollup-plugin-typescript2rollup 和 ts的 桥梁
@rollup/plugin-node-resolve解析node第三方模块
@rollup/plugin-json支持引入json
execa开启子进程方便执行命令
npm install typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa -D

4.1.2 workspace配置

npm init -y && npx tsc --init
{
  "private":true,
  "workspaces":[
    "packages/*"
  ]
}

目录结构配置

C:.
│  package.json        # 配置运行命令 
│  rollup.config.js    # rollup配置文件
│  tsconfig.json       # ts配置文件 更改为esnext
│  yarn.lock
│  
├─packages             # N个repo
│  └─reactivity
│      │  package.json
│      └─src
│          index.ts
│              
└─scripts              # 打包命令
        build.js

配置模块名称及打包选项

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "module": "dist/reactivity.esm-bundler.js",
  "author": "",
  "license": "ISC",
  "buildOptions":{
    "name":"VueReactivity",
    "formats":[
      "esm-bundler",
      "cjs",
      "global"
    ]
  }
}

创建软链yarn install

4.1.3 对 packages 下模块进行打包

scripts/build.js
const fs = require('fs');
const execa = require('execa')
// 过滤packages目录下所有模块
const targets = fs.readdirSync('packages').filter(f => {
    if (!fs.statSync(`packages/${f}`).isDirectory()) {
        return false;
    }
    return true;
})
// 开始并行打包
runParallel(targets, build)
async function runParallel(source, iteratorFn) {
    const ret = [];
    for (const item of source) {
        const p = iteratorFn(item)
        ret.push(p);
    }
    return Promise.all(ret);
}
async function build(target) {
    await execa(
        'rollup',
        [
            '-c',
            '--environment',
            `TARGET:${target}`
        ], 
        { stdio: 'inherit' }
    )
}

4.1.4 rollup配置

rollup.config.js
import path from 'path';
import ts from 'rollup-plugin-typescript2'
import json from '@rollup/plugin-json'
import resolvePlugin from '@rollup/plugin-node-resolve'
const packagesDir = path.resolve(__dirname, 'packages'); // 获取packages目录


const packageDir = path.resolve(packagesDir, process.env.TARGET); // 获取要打包的目标目录
const name = path.basename(packageDir); // 获取打包的名字

const resolve = p => path.resolve(packageDir, p);
const pkg = require(resolve(`package.json`)) // 获取目标对应的package.json

const packageOptions = pkg.buildOptions; // 打包的选项
const outputConfigs = {
    'esm-bundler': {
        file: resolve(`dist/${name}.esm-bundler.js`), // webpack打包用的
        format: `es`
    },
    'cjs': {
        file: resolve(`dist/${name}.cjs.js`), // node使用的
        format: 'cjs'
    },
    'global': {
        file: resolve(`dist/${name}.global.js`), // 全局的
        format: 'iife'
    }
}

function createConfig(format, output) {
    output.name = packageOptions.name;
    output.sourcemap = true;
    return {
        input: resolve(`src/index.ts`), // 入口
        output,
        plugins:[
            json(),
            ts({
                tsconfig:path.resolve(__dirname,'tsconfig.json')
            }),
            resolvePlugin(),
        ]
    }
}
// 根据模块配置信息选择性打包
export default packageOptions.formats.map(format => createConfig(format, outputConfigs[format]));

4.1.5 开发环境打包

const execa = require('execa')
const target = 'reactivity'
execa('rollup', [
        '-wc',
        '--environment',
        `TARGET:${target}`
    ], {
        stdio: 'inherit'
    }
)

4.2 packages/reactivity/src 下文件说明

文件名含义
index.ts入口文件,导入导出文件
reactive.ts包含了reactive、shallowReactive、readonly、shallowReadonly等方法文件
basehandles.ts公共的Proxy中的get和set方法文件
effect.ts收集依赖和通知依赖更新文件
ref.ts包含了ref、shallowRef、toRef、toRefs等方法文件
computed.ts包含了computed等方法文件

4.3 packages/shared/src 下文件说明

文件名含义
index.ts公共的工具函数,判断对象,数组等等方法
opeartors.ts公共的枚举常量,依赖收集和更新等等

4.4 reactive 、shallowReactive、readonly、shallowReadonly 实现

4.4.1 reactive.ts文件中

import { mutableHandles, shallowReadonlyHandles, shallowReactiveHandles, readonlyHandles } from './basehandles'
import { isObject } from '@vue/shared'

// 返回一个代理对象,深度代理,可修改
export function reactive(target) {
  return createReactiveObject(target, false, mutableHandles)
}
// 返回一个代理对象,浅层代理,可修改
export function shallowReactive(target) {
  return createReactiveObject(target, false, shallowReactiveHandles)
}
// 返回一个代理对象,深度代理,不可被修改
export function readonly(target) {
  return createReactiveObject(target, true, shallowReadonlyHandles)
}
// 返回一个代理对象,浅层代理,未被代理的可以被修改
export function shallowReadonly(target) {
  return createReactiveObject(target, true, readonlyHandles)
}
const reactiveMap = new WeakMap;
const readonlyMap = new WeakMap;

/**
 * 
 * @param target 要被代理的对象
 * @param isReadOnly 是否为只读
 * @param handles proxy中的处理逻辑(get/set)
 */
function createReactiveObject(target, isReadOnly, handles) {
  // 如果要被代理的对象不是一个对象,那么返回原对象
  if (!isObject(target)) return target
  // 先从缓存中读取结果,如果已经被存过了,那么直接返回缓存的代理结果即可
  const proxyMap = isReadOnly ? readonlyMap : reactiveMap
  const isExitsProxy = proxyMap.get(target)
  if (isExitsProxy) {
    return isExitsProxy
  }
  // 对象如果被代理过,就不用再次代理,那么我们需要将代理过的结果缓存起来
  const proxy = new Proxy(target, handles)
  // 缓存代理结果,形成原目标和代理目标的映射关系
  proxyMap.set(target, proxy)
  // 返回代理对象
  return proxy
}

4.4.2 baseHandles.ts文件中

import { extend, stringify, isObject, isArray, IntegerKey, hasOwn, hasChanged } from "@vue/shared"
import { readonly, reactive } from "./reactive"
import { OpeaTypes, TriggerTypes } from "packages/shared/src/opeartors"
import { track, trigger } from "./effect"

/**
 * 
 * @param readonly 是否为只读的,默认不是
 * @param shallow 是否为浅层的,默认不是
 */
const createGetter = function (isReadonly = false, shallow = false) {
  // 真实的get函数,当读取代理对象中的值时,会触发此函数
  /**
   * @param target 目标对象
   * @param property 被获取的属性名
   * @param receiver 代理对象
   */
  return function get(target, property, receiver) {
    const res = Reflect.get(target, property, receiver)
    // 如果不是只读的,就说明要收集对应的依赖,因为只读的话不能被修改,所以不需要收集依赖,这些依赖后面会去更新我们对应的视图
    if (!isReadonly) {
      // 收集依赖...
      track(target, OpeaTypes.GET, property)
    }
    // 如果是浅层代理,那么直接返回结果即可,不需要继续进行代理了
    if (shallow) {
      return res
    }
    // 如果取到的值还是一个对象,那么我们要递归进行代理
    if (isObject(res)) {
      // 看是否为只读的
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
const createSetter = function (shallow = false) {
  /**
   * @param target 目标对象
   * @param property 被获取的属性名
   * @param value 新属性值
   * @param receiver 代理对象
   */
  return function set(target, property, value, receiver) {
    // 拿到老值,后面实现watch的时候会用到
    let oldValue = target[property]
    // 判断是新增还是修改,可能是数组,可能是对象,因为reactive包裹的是一个对象
    const hadKey = isArray(target) && IntegerKey(property)
      ? Number(property) < target.length
      : hasOwn(target, property)
    const res = Reflect.set(target, property, value, receiver)
    if (!hadKey) {
      // 新增
      trigger(target, TriggerTypes.ADD, property, value)
    } else if (hasChanged(oldValue, value)) {
      // 修改
      trigger(target, TriggerTypes.SET, property, value, oldValue)
    }
    return res
  }
}
const readonlyObj = {
  set: function (target, key) {
    console.warn(`set ${key} on ${stringify(target)} failed`)
  }
}
export const mutableHandles = {
  get: createGetter(),
  set: createSetter()
}
export const shallowReactiveHandles = {
  get: createGetter(false, true),
  set: createSetter(true)
}
export const readonlyHandles = extend({
  get: createGetter(true, false),
}, readonlyObj.set)
export const shallowReadonlyHandles = extend({
  get: createGetter(true, true)
}, readonlyObj.set)

4.4.3 effect.ts文件中

import { isArray } from "@vue/shared"
import { TriggerTypes } from "packages/shared/src/opeartors"

// effect(() =>{},{flush:'sync'}) 立即执行一次
export const effect = function (fn, options: any = {}) {
  // 高阶函数返回新的effect函数
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    // 因为一上来就执行了一次,所以我们这里执行了effect调用就是相当于执行了createReactiveEffect中的effect函数
    effect()
  }
  return effect
}
let uid = 0, activeEffect, effectStack = []
function createReactiveEffect(fn, options) {
  const effect = function () {
    /**
     * 我们可能会写出这样的代码
     * effect(() =>{  ---> effect1
     *    state.name
     *    effect(() =>{ ---> effect2
     *         state.age
     *    })
     *    state.address
     * })
     * 当我们进行第一次取值state.name时,name和effect1进行关联
     * 当我们进行第二次取值state.age时,age和effect2进行关联
     * 但是当我们进行第三次取值state.address时,此时的address和effect2关联了,就不对了
     * 因为effect的调用就是一个类似函数的调用栈,所以我们可以用一个栈形结构来维护key和effect的关系
     * 我们调用用户的函数可能会发生异常
     */
    try {
      effectStack.push(effect)
      activeEffect = effect
      // const fn = () => {
      //   console.log(state.name + state.age)
      // }
      // effect(fn)
      // 函数调用,会进行取值,我们需要收集对应的依赖关系,后续当状态发生改变,我们可以通知视图去更新,类似于Vue2中的 Dep / Watcher
      // effect的返回值就是函数调用的返回值
      // 取值走get
      return fn()
    } finally {
      // 调用完函数从栈中抛出
      effectStack.pop()
      // 让我们下一个的effect指向正确的effect
      activeEffect = effectStack[effectStack.length - 1]
    }
  }
  effect.uid = uid++ // effect的唯一标识
  effect._isEffect = true // 标识是否为响应式effect
  effect.raw = fn // 将用户回调函数和effect做一个关联
  effect.options = options // 储存用户的配置选项
  return effect
}
let targetMap = new WeakMap;
/**
 * 
 * @param target 目标对象
 * @param type 唯一标识
 * @param key 对象的key
 */
export function track(target, type, key) {
  // 要将key和对应的effect进行关联,我们用一个全局变量
  // 因为我们只有在effect中使用状态才会进行依赖收集,在外界使用我们是不管的,而每次get时都会触发此方法,所以我们需要判断一下activeEffect是否有值
  // 有值就说明是在effect中使用的状态
  if (activeEffect) {
    // 我们需要将key和effect进行关联,而key也应该和对应的目标对象进行关联,effect可能有多个,也有可能会重复,所以这里的关系是这样的
    // (WeakMap target) => (Map key => Set effect)
    let depsMap = targetMap.get(target)
    // 第一次WeakMap中肯定没有target目标对象
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map))
    }
    let deps = depsMap.get(key)
    // 第一次Map中肯定没有key
    if (!deps) {
      depsMap.set(key, (deps = new Set))
    }
    // 第一次Set中肯定没有effect
    if (!deps.has(key)) {
      deps.add(activeEffect)
    }
    // 这样我们的关系就建立了,等到用户修改数据时,我们通知对应的effect重新执行即可
  }
}
/**
 * 
 * @param target 目标对象
 * @param type 标识是新增还是修改,0新增,1修改
 * @param key 要对哪个key进行操作
 * @param value 操作后的结果值
 * @param oldValue 操作前的结果值
 */
export function trigger(target, type, key, value, oldValue?) {
  // 如果没有收集过对应的依赖,那么是不需要进行更新的
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  // 用于存放要执行的effect函数集合
  const effects = new Set
  const add = (deps) => {
    if (deps) {
      deps.forEach(effect => effects.add(effect))
    }
  }
  // 说明改的是数组的length
  if (key === 'length' && isArray(target)) {
    // 我们需要循环depsMap,将要执行的effect全部添加到容器中
    depsMap.forEach((dep, key) => {
      // key > value 是这种情况
      /**
       * const state = reactive({arr:[1,2,3]})
      *  effect(() => console.log(state.arr[2]))
      *  setTimeout(() =>{ state.arr.length = 1 },1000)
      * 此时的key为2, value是1 ,也要进行更新
       */
      if (typeof key !== 'symbol') {
        if (key === 'length' || key > value) {
          add(dep)
        }
      }
    })
  } else {
    // 对象
    if (key !== undefined) {
      add(depsMap.get(key))
    }
    // 如果修改数组中的某一个索引,也要更新
    switch (type) {
      case TriggerTypes.ADD:
        // 表示是新增,通知length的effect去更新
        add(depsMap.get('length'))
    }
  }
  // 让effect更新
  effects.forEach((effect: any) => effect())
}

4.4.4 shared/index.ts文件中

// 判断是否为对象类型
export const isObject = val => typeof val === 'object' && val !== null
// 合并方法
export const extend = Object.assign
// JSON.stringify
export const stringify = JSON.stringify
// 判断是否为数组
export const isArray = Array.isArray
// 判断是否为一个整型key
export const IntegerKey = key => parseInt(key) + '' === key
// 判断是否为一个函数
export const isFunc = val => typeof val === 'function'
// 判断是否是自身上的属性
export const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key)
// 判断2个值是否不相等
export const hasChanged = (oldValue, value) => oldValue !== value
// 打印
export const warn = val => console.warn(val)

4.5 ref 、shallowRef 实现

import { track, trigger } from "./effect"
import { TrackTypes } from "packages/shared/src/opeartors"
import { hasChanged, isArray } from "@vue/shared"
import { isObject } from "@vue/shared"
import { reactive } from "./reactive"

function createRef(rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow)
}
const convert = val => isObject(val) ? reactive(val) : val;
class RefImpl {
  // 声明属性
  public _v_isRef = true // 标识是一个ref属性
  public _value
  // 简写:相当于在内部 this.rawValue = rawValue;this.shallow = shallow
  constructor(public rawValue, public shallow) {
    // ref可以接收对象类型,如果接收的是对象类型,需要定义成响应式
    this._value = shallow ? rawValue : convert(rawValue)
  }
  // 类的属性访问器,编译后为Object.defineProperty
  get value() {
    // 外界: let r = ref(''); 
    // 当外界去访问 r.value 时,要收集相关依赖 ==> track
    // 当外界去设置 r.value 时,要通知更新 ==> trigger
    // r.value 访问的是 this._value
    // 这样我们使用r.value时,value就会和对应的effect进行关联
    // 关联关系: RefImpl的实例 => value => [effect]
    track(this, TrackTypes.GET, 'value')
    return this._value
  }
  set value(newValue) {
    // 通知更新
    if (hasChanged(this.rawValue, newValue)) {
      // 这次的新值当成下一次的老值
      this._value = this.shallow ? newValue : convert(newValue)
      this.rawValue = newValue
      trigger(this, TrackTypes.SET, 'value', newValue, this.rawValue)
    }
  }
}
export function ref(rawValue) {
  return createRef(rawValue)
}
export function shallowRef(rawValue) {
  return createRef(rawValue, true)
}

4.6 toRef、toRefs 实现

import { track, trigger } from "./effect"
import { TrackTypes } from "packages/shared/src/opeartors"
import { hasChanged, isArray } from "@vue/shared"
import { isObject } from "@vue/shared"
import { reactive } from "./reactive"

function createRef(rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow)
}
const convert = val => isObject(val) ? reactive(val) : val;
class RefImpl {
  // 声明属性
  public _v_isRef = true // 标识是一个ref属性
  public _value
  // 简写:相当于在内部 this.rawValue = rawValue;this.shallow = shallow
  constructor(public rawValue, public shallow) {
    // ref可以接收对象类型,如果接收的是对象类型,需要定义成响应式
    this._value = shallow ? rawValue : convert(rawValue)
  }
  // 类的属性访问器,编译后为Object.defineProperty
  get value() {
    // 外界: let r = ref(''); 
    // 当外界去访问 r.value 时,要收集相关依赖 ==> track
    // 当外界去设置 r.value 时,要通知更新 ==> trigger
    // r.value 访问的是 this._value
    // 这样我们使用r.value时,value就会和对应的effect进行关联
    // 关联关系: RefImpl的实例 => value => [effect]
    track(this, TrackTypes.GET, 'value')
    return this._value
  }
  set value(newValue) {
    // 通知更新
    if (hasChanged(this.rawValue, newValue)) {
      // 这次的新值当成下一次的老值
      this._value = this.shallow ? newValue : convert(newValue)
      this.rawValue = newValue
      trigger(this, TrackTypes.SET, 'value', newValue, this.rawValue)
    }
  }
}
class ObjectRefImpl {
  public _v_isRef = true
  constructor(public target, public key) {

  }
  get value() {
    return this.target[this.key]
  }
  set value(newValue) {
    this.target[this.key] = newValue
  }
}
// 将一个值包装成ref对象,是否为响应式取决于原来的值是否是响应式
export function toRef(target, key) {
  return new ObjectRefImpl(target, key)
}
// 将多个值包装成ref对象,是否为响应式取决于原来的值是否是响应式
export function toRefs(target) {
  // const r = toRefs(state)
  // target可能是数组,可能是对象
  const res = isArray(target) ? new Array(target.length) : {}
  for (let key in target) {
    res[key] = toRef(target, key)
  }
  return res
}

4.7 computed 实现

4.7.1 computed.ts文件

import { isFunc, warn } from "@vue/shared"
import { effect, track, trigger } from "./effect"
import { TrackTypes } from "packages/shared/src/opeartors"

/*
      计算属性特点:
        默认不会执行,当取值时才会执行
        有缓存,如果状态没有发生变化,不会重新执行函数,会返回上一次值
        可以传入一个函数,这个函数就是getter函数
        也可以传入一个配置项,配置项中包含get和set
        computed(() =>{})
        computed({get(){},set(){}})
*/
export function computed(getterOrOptions) {
  let getter, setter
  if (isFunc(getterOrOptions)) {
    // 是函数的情况
    getter = getterOrOptions
    setter = () => warn('Write operation failed: computed value is readonly')
  } else {
    // 是配置项的情况
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  return new ComputedRefImpl(getter, setter)
}
class ComputedRefImpl {
  // 标识计算属性getter的返回值
  public _value
  // 标识是一个Ref
  public _v_isRef = true
  // 默认是脏的,通过此变量来控制是否需要缓存
  public _dirty = true
  public effect
  constructor(public getter, public setter) {
    // 默认getter不会执行,只有取值时才会执行
    this.effect = effect(getter, {
      lazy: true, // 默认不执行
      scheduler: () => {
        // 说明变化过
        if (!this._dirty) {
          this._dirty = true
          // 通知更新
          trigger(this, TrackTypes.SET, 'value')
        }
      }
    })
  }
  /**
   * 外界是这么访问的
   * const state = reactive({age:10})
   * const c = computed(() => state.age + 10)
   * c.value ==> 20
   */
  // 当读取c.value时,要调用我们的getter函数,函数的返回值作为我们的_value值
  get value() {
    // 要看一下依赖是否变化过,依赖变化过我们的dirty变量就为false
    // 计算属性中依赖的响应式数据如果发生变化了,当我们再次取值时会重新执行getter函数
    // 那么我们需要收集getter中的依赖
    if (this._dirty) {
      // effect的返回值就是用户回调的返回值
      this._value = this.effect()
      // 缓存,下一次在取值就进入不到此判断中,会返回上一次的值
      this._dirty = false
    }
    // 收集依赖
    track(this, TrackTypes.GET, 'value')
    return this._value
  }
  set value(newVal) {
    this.setter(newVal)
  }
}

4.7.2 effect.ts文件中修改

// 让effect更新
effects.forEach((effect: any) => {
    if (effect.options.scheduler) {
      return effect.options.scheduler()
    }
    return effect()
})

5. 总结

看了Vue2和Vue3源码中响应式处理这一部分,很明显的感觉到Vue3对于响应式数据处理这一块代码上简洁了很多,所有的功能都可以拆开然后单独使用,不需要反复横跳及切换文件。

个人去实现响应式Api并不是为了证明给谁看,而是去学习人家背后的设计思想,去学人家的编码设计,从而让我们在使用这些Api的过程中更加得心应手,在工作中,不可避免的会产生Bug,如果Bug的层次比较深,在相关的网站上查询不到这些问题,那么只能通过调试源码的方式去解决问题。

前端技术更新很快,我们在学习的过程中要以 知其所以然 的态度去对待,这样才能保证自己的核心竞争力及工作中较为不错的编码能力和架构思维

本文有写的不好的地方还请指出,多多包涵,不喜勿喷!