vue3 响应式原理的实现,源码地址:vue3_reactivity

rollup 搭建ts环境

装置 rollup 插件

npm install rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-serve typescript -D
包名性能
rollup打包工具
rollup-plugin-typescript2解析ts插件
@rollup/plugin-node-resolve解析第三方模块
@rollup/plugin-replace替换插件
rollup-plugin-serve启动本地服务插件

配置打包环境

生成 tsconfig.json 文件:

npx tsx --init

批改 tsconfig.json 配置属性 moduleESNext(默认在浏览器运行)

能够设置 strict 属性为false,让 ts 反对 any 类型,scouceMap 须要设置成 true,不便调试代码

根目录新建 rollup.config.js 配置文件,并输出上面内容:

import path from "path";import ts from "rollup-plugin-typescript2";import { nodeResolve } from "@rollup/plugin-node-resolve";import replace from "@rollup/plugin-replace";import serve from "rollup-plugin-serve";export default {  input: 'src/index.ts',  output: {    name: 'VueReactivity',    format: 'umd',    file: path.resolve('dist/VueReactivity.js'),    sourcemap: true,  },  plugins: [    nodeResolve({      extensions: ['.js', '.ts'],    }),    ts({      tsconfig: path.resolve(__dirname, 'tsconfig.json'),    }),    replace({        "process.env.NODE_ENV": JSON.stringify("development"),    }),    serve({        open: true,        openPage: "/public/index.html",        port: 3000,        contentBase: ""    })  ],}

新建入口文件srx/index.ts和模板文件public/index.html

模板文件 index.html 须要手动引入打包后的 /dist/vue.js

package.json 增加打包命令:

"scripts": {  "dev": "rollup -c -w"}

reactivity模块

先看看Vue的reactivity模块实现成果,先装置 reactivity:

npm install @vue/reactivity

测试 public/index.html 内容:

<div id="app"></div><script src="/node_modules/@vue/reactivity/dist/reactivity.global.js"></script><script>    const {reactive, effect} = VueReactivity;    const state = reactive({name:"chenwl",age:12,address:"guangzhou1"});    effect(()=>{        app.innerHTML = `${state.name} 往年 ${state.age} 岁`    });        // 当 effect 函数中依赖的数据发生变化 effect 会从新执行    setTimeout(() => {        state.name = "change"    }, 1000);</script>

外围:当读取文件时,做依赖收集,当数据变动时从新执行effect

初始化目录构造

-src - reactivity     - effect.ts     - reactive.ts     - index.ts - shared     - index.ts //通用办法 - index.ts

reactivity/index.ts

export { reactive } from './reactive'export { effect } from './effect'

src/index.ts

export * from "./reactivity/index"

创立响应式对象

reactive/reactive.ts

import { isObject } from "../shared/index"const mutableHandlers = {    get(){},    set(){}}export function reactive(target){    // 将指标对象变成响应式对象,Proxy    return createReactiveObject(target, mutableHandlers)}// 外围:当读取文件时,做依赖收集,当数据变动时从新执行effectfunction createReactiveObject(target, baseHandlers) {   // 如果是不是对象,间接返回  if(!isObject(target)) return target;      return new Proxy(target, baseHandlers)}

简略的实现下 isObject 办法:

export const isObject = (val) => typeof val === 'object' && val !== null

Proxy劣势:

  • Proxy 间接监听对象而非属性,只是对外层对象做代理,默认不会递归,不会重写对象中的属性
  • Proxy 能够间接监听数组的变动
  • Proxy 返回的是一个新对象,咱们能够只操作新的对象达到目标,而 Object.defineProperty 只能遍历对象属性间接批改

创立映射表

为避免对象被反复代理,这里应用WeakMap创立代理元素的映射表,如果对象被代理过,则间接返回:

// 映射表const proxyMap = new WeakMap()function createReactiveObject(target, baseHandlers) {  if (!isObject(target)) return target    // 从映射表中取出,判断是否被代理过  const existingProxy = proxyMap.get(target)  if (existingProxy) return existingProxy  const proxy = new Proxy(target, baseHandlers)  // 放入代理对象  proxyMap.set(target, proxy)  return proxy}
WeakMap 绝对于 Map 也是键值对汇合,然而 WeakMap 的key 只能是非空对象(non-null object),WeakMap 对它的 key 仅放弃弱援用,也就是说它不阻止垃圾回收器回收它所援用的 key,WeakMap 最大的益处是能够防止内存透露。一个仅被 WeakMap 作为 key 而援用的对象,会被垃圾回收器回收掉。

代理工厂函数

为了方便管理代理逻辑,这里拆分 mutableHandlers 对象到新文件/reactivity/haseHandler.ts 中。

reactivity/haseHandler.ts

function createGetter() {  return function get(target, key, reaciver) {}}function createSetter() {  return function set(target, key, value, receiver) {}}const get = createGetter();const set = createSetter()export const mutableHandlers = {  get,  // 获取对象会执行此办法  set, // 设置属性值会执行此办法}
set 和 get 办法通过工厂函数创立,工厂函数的能够不便参数的传参和预置

reactive.ts 文件中引入:

import { mutableHandlers } from './baseHandler'export function reactive(target) {  // 将指标对象变成响应式对象,Proxy  return createReactiveObject(target, mutableHandlers)}

getter 办法

当代理对象的属性被获取时:

function createGetter() {  return function get(target, key, reaciver) {    const res = Reflect.get(target, key, reaciver) // 相当于 target[key];        // 不对 symbol 类型做解决    if (typeof key === 'symbol') return res;    console.log('此时代理对象的属性被获取')        // 如果是对象,进行递归代理    if (isObject(res)) return reactive(res);    return res  }}

setter 办法

在对属性进行设置之前,须要判断是批改值还是新增值,并且须要留神,如果是数组,须要判断批改的形式是否通过索引增加:

let arr = [1];arr[10] = 10; // 通过索引新增值的数组

所以判断之前,还须要对target进行判断,如果是数组,须要减少索引判读。

数组索引比原数组长度小 ? 批改 : 新增

通过target[key]先获取旧值,而后再跟新值比对判断。

代码逻辑:

function createSetter() {  return function set(target, key, value, receiver) {    const oldValue = target[key] // 获取旧值,看下有没有这个属性        // 如果是数组,依据索引判断是批改还是新增    const hasKey =      isArray(target) && isInteger(key)        ? Number(key) < target.length        : hasOwn(target, key)    const result = Reflect.set(target, key, value, receiver);        if(!hasKey){        console.log("新增属性");    }else if (hasChanged(value, oldValue)) {      console.log('批改属性')    }        return result  }}

通用办法:

export const isArray = Array.isArrayexport const isInteger = (key) => '' + parseInt(key, 10) === keyconst hasOwnProperty = Object.prototype.hasOwnProperty;export const hasOwn = (val, key) => hasOwnProperty.call(val,key);export const hasChanged = (value, oldValue) => value !== oldValue

批改模板 public/index.html 下的援用,能够看到控制台输入对应的属性设置操作。

<script src="../dist/VueReactivity.js"></script><script>  const { reactive, effect } = VueReactivity  const state = reactive({ name: 'chenwl', age: 12, address: 'guangzhou1' })  state.name = 'change' // 批改属性  state.newProp = 'newProp' // 新增属性  const stateArr = reactive(['a', 'b', 'c'])  stateArr[0] = 'array change' // 数组批改  stateArr[3] = 'add array' // 数组新增</script>

effect 函数

回到开始写的 public/index.html 内容:

<div id="app"></div><script src="../dist/VueReactivity.js"></script><script>    const { reactive, effect } = VueReactivity    const state = reactive({ name: 'chenwl', age: 12 })    effect(()=>{        app.innerHTML = `${state.name} 往年 ${state.age} 岁`    });    setTimeout(() => {        state.name = 'change' // 批改属性    }, 1000)</script>

页面初始化后,app 标签的内容为 chenwl 往年 12 岁,一秒后批改为:change 往年 12 岁

当代理对象的值产生扭转时,effect函数参数外面用户自定义的办法也会执行

初始化 effect 函数

下面的逻辑能够失去,effect 办法第一个参数为用户自定义的办法,外面寄存用户本人的逻辑,这个办法在上面的状况下会执行:

  • options.lazy 为 false,初始化时执行
  • 呈现在自定义函数外面的代理对象产生扭转

批改 effect.ts 如下:

export function effect(fn, options: any = {}) {  const effect = createReactiveEffect(fn, options)  if (!options.lazy) {    effect()  }  return effect}function createReactiveEffect(fn, options) {  const effect = function () {    return fn() // 用户本人写的逻辑,外部会对数据进行取值操作  }  return effect}

收集依赖

申明变量 activeEffect 存储以后执行的 effect 函数:

let activeEffect; // 用来存储以后的effect函数function createReactiveEffect(fn, options) {  const effect = function () {    activeEffect = effect    return fn()  }  return effect}
fn 函数执行时,函数上下文的响应式变量会做取值(getter)操作,此时能够通过activeEffect获取以后响应式变量关联的effect
// fn函数执行,触发响应式变量`state`的取值操作effect(() => {  app.innerHTML = `${state.name} 往年 ${state.age} 岁`})...// baseHandler.tsfunction createGetter(){    return function get(target, key, reaciver) {        // 触发取值操作    }}

track依赖收集

为了将响应式属性和effect进行关联,这里申明 track 函数进行依赖收集:

// effect.tsexport function track(target,key){    if(activeEffect === undefined) return;}

当调用fn()时,会执行用户传入的函数,此时会进行取值操作,咱们在这里实现依赖收集性能:

// baseHandler.ts function createGetter() {  return function get(target, key, reaciver) {    console.log('此时代理对象的属性被获取')    track(target, key)  }}

建设映射表,存储 effect 更新函数 和 响应式属性 的关系:

// 映射表const targetMap = new WeakMap(); // targetMap = {target:{key:[effect,effect]}}// 属性和effect关联export function track(target, key) {  if (!activeEffect) return  let depsMap = targetMap.get(target)  if (!depsMap) {    targetMap.set(target, (depsMap = new Map()))  }  let dep = depsMap.get(key)  if (!dep) {    depsMap.set(key, (dep = new Set()))  }  if (!dep.has(activeEffect)) {    dep.add(activeEffect)    activeEffect.deps.push(dep)  }}
 let activeEffect; // 用来存储以后的effect函数+let uid = 0;function createReactiveEffect(fn, options) {  const effect = function () {      activeEffect = effect      return fn()  }+  effect.id = uid++ // effect标识+  effect.deps = [] // 用来示意 effect 中依赖了哪些属性+  effect.options = options // effect中参数  return effect}

清空 activeEffect

当依赖收集实现,须要清空以后的 activeEffect 办法:

function createReactiveEffect(fn, options) {  const effect = function () {+    try {      activeEffect = effect      return fn()+    } finally {+      activeEffect = null+    }  }  ...  return effect}export function track(target, key) {  if (!activeEffect) return; // 不存在或被清空不执行映射关系存储}

然而如果呈现上面的状况:

effect(()=>{    state.name;    effect(()=>{        state.age    });    state.address})

外部的effect办法在收集完依赖后,就会清空activeEffect办法,导致最初的state.address 没有被收集。

栈构造清空,保障清空的是最初一个effect

 let activeEffect let uid = 0+ const effectStack = []function createReactiveEffect(fn, options) {  const effect = function () {    try {      activeEffect = effect+      effectStack.push(activeEffect)      return fn()    } finally {+      effectStack.pop()+      activeEffect = effectStack[effectStack.length - 1]    }  }  effect.id = uid++  effect.deps = []  effect.options = options  return effect}

解决死循环:

effect(() => {  state.age++  app.innerHTML = `${state.name} 往年 ${state.age} 岁`})

state.age始终在变动会导致effect一直的递归执行,为避免这种状况,如果effectStack存储了同样的effect略过:

const effectStack = []function createReactiveEffect(fn, options) {  const effect = function () {+    if (effectStack.includes(effect)) return    try {} finally {}  }  ...}

trigger触发更新

依赖收集后,接下来触发函数更新,这里实现trigger函数触发更新:

export enum TriggerType {  add = 'add',  set = 'set',}export function trigger(target, type:TriggerType, key, value?, oldValue?) {  const depsMap = targetMap.get(target)  if (!depsMap) return  const run = (effects) => {    if (effects) effects.forEach((effect) => effect())  }  if (key != void 0) {    run(depsMap.get(key))  }}

设置响应式属性时,触发 trigger

function createSetter() {  return function set(target, key, value, receiver) {    const oldValue = target[key]     ...    if (!hasKey) {      // 新增属性+      trigger(target, TriggerType.add, key, value)    } else if (hasChanged(value, oldValue)) {      // 批改属性+      trigger(target, TriggerType.set, key, value, oldValue)    }    ...  }}

数组触发的更新

状况一:收集和批改都是数组属性(length)

const state = reactive([1, 2, 3])effect(() => {  app.innerHTML = state.length})setTimeout(() => {  state.length = 100}, 1000)

后果:触发更新

状况二:批改数组长度,没有收集数组属性

const state = reactive([1, 2, 3])effect(() => {  app.innerHTML = state[2]})setTimeout(() => {  state.length = 1}, 1000)

后果:属性批改,没有触发更新

批改条件判断:

if (key === 'length' && isArray(target)) {  depsMap.forEach((dep, key) => {    if (key === 'length' || key >= value) {      run(dep)    }  })} else {  if (key != void 0) {    run(depsMap.get(key))  }}

状况三:通过索引减少数组选项,收集数组长度小于批改长度

const state = reactive([1, 2, 3])effect(() => {  app.innerHTML = state})setTimeout(() => {  state[10] = 10}, 1000)

后果:通过索引批改,没有触发更新

增加条件判断:

switch (type) {  case 'add':    if (isArray(target)) {      // 数组通过索引减少选项      if (isInteger(key)) {        run(depsMap.get('length'))      }    }}

残缺的 trigger 函数:

export enum TriggerType {  add = 'add',  set = 'set',}export function trigger(target, type: TriggerType, key, value?, oldValue?) {  const depsMap = targetMap.get(target)  if (!depsMap) return  const run = (effects) => {    if (effects) effects.forEach((effect) => effect())  }  if (key === 'length' && isArray(target)) {    depsMap.forEach((dep, key) => {      if (key === 'length' || key >= value) {        run(dep)      }    })  } else {    if (key != void 0) {      run(depsMap.get(key))    }    switch (type) {      case 'add':        if (isArray(target)) {          // 数组通过索引减少选项          if (isInteger(key)) {            run(depsMap.get('length'))          }        }    }  }}

响应式过程

通过上面的例子来回顾下vue3响应式执行的过程

const { reactive, effect } = VueReactivity// reactive 办法将参数变成响应式对象const state = reactive({ name: 'chenwl' })// effect 外部如何操作effect(() => {  app.innerHTML = `姓名:${state.name}`})setTimeout(() => {  state.name = 'change'}, 1000)

首先 reactive 将参数变成响应式对象并返回,接着就是effect函数的执行过程

let activeEffect;const effect = function (fn){    console.log("1、effect 函数执行");    try{        console.log("2、保留以后effect函数到 activeEffect");        activeEffect = effect;        console.log("3、fn 函数执行");        fn()    }finally{        // 清空 activeEffect    }}

fn() 函数执行,state.name 作为响应式属性会进入它的getter拜访器:

function createGetter() {  return function get(target, key, reaciver) {    const res = Reflect.get(target, key, reaciver) // 相当于 target[key];    if (typeof key === 'symbol') return res // 不对 symbol 类型做解决    console.log(`4、进入 ${key} => getter 拜访器`)    track(target, key)    if (isObject(res)) return reactive(res) // 如果是对象,进行递归代理    return res  }}

getter 拜访器外面,track 会收集以后属性所依赖的effect函数:

const targetMap = new WeakMap()export function track(target, key) {  if (activeEffect == undefined) return  let depsMap = targetMap.get(target)  if (!depsMap) {    targetMap.set(target, (depsMap = new Map()))  }  let dep = depsMap.get(key)  if (!dep) {    depsMap.set(key, (dep = new Set()))  }  if (!dep.has(activeEffect)) {    dep.add(activeEffect)    activeEffect.deps.push(dep)  }  // targetMap = { target: { key: [effect, effect] } }  console.log(`5、${key} => 收集依赖:`, targetMap)}

state.name 产生批改操作,进入到响应式属性的设置办法并触发trigger更新办法:

function createSetter() {  return function set(target, key, value, receiver) {    const result = Reflect.set(target, key, value, receiver)        console.log(`6、${key} => 批改属性`)    trigger(target, TriggerType.set, key, value, oldValue)        return result  }}

trigger办法外面找到targetMap对应的target.key,获取以后响应式属性所在的effect函数并执行更新操作

export function trigger(target, type: TriggerType, key, value?, oldValue?) {  console.log(`8、${key} => 触发更新`)  const run = (effects=[]) => {    effects.forEach(effect=>{        console.log(`9、获取${key} => targetMap的effect执行`)        console.log('===== 进入key存储的effect =====')        effect();    })  }  if (key != void 0) {    run(depsMap.get(key))  } }

控制台打印后果:

1、effect2、保留以后effect函数到activeEffect3、fn函数执行4、进入 name => getter 拜访器5、name => 收集依赖: WeakMap {{…} => Map(1)}6、name => 批改属性7、name => 触发更新8、获取name => targetMap的effect执行===== 进入key存储的effect =====1、effect2、保留以后effect函数到activeEffect3、fn函数执行4、进入 name => getter 拜访器5、name => 收集依赖: WeakMap {{…} => Map(1)}

计算属性 Computed

computed 应用

计算属性 computed 的应用:

<div id="app"></div><script src="/node_modules/@vue/reactivity/dist/reactivity.global.js"></script><script>  const { reactive, effect, computed } = VueReactivity  const state = reactive({ name: 'chenwl', age: 12 })  const birth_year = computed(() => {    return new Date().getFullYear() - state.age  })  effect(() => {    app.innerHTML = `${state.name} 出生于 ${birth_year.value} 年`  })  setTimeout(() => {    state.age++  }, 1000)</script>

state.age 的值发生变化时,依赖于它的 birth_year 会从新执行计算属性。

computed 返回值

通过打印 birth_year 能够在控制台看到它的值:

ComputedRefImpl = {  __v_isReadonly: true,  __v_isRef: true,  _dirty: true,  setter: ƒ,  effect: ƒ,  _value: 2008,  value: 2008,}
默认计算属性的值被包装到了value属性上

初始化 computed

新建 reactivity/computed.ts 并导出:

export function computed(getterOrOptions) {  let getter  let setter  if (isFunction(getterOrOptions)) {    getter = getterOrOptions    setter = () => console.warn('computed not set value')  } else {    getter = getterOrOptions.get    setter = getterOrOptions.set  }}

computed 接管一个参数,这个参数可能是函数也可能是 {getter,setter} 对象,初始化函数并对参数进行判断。

ComputedRefImpl 类

通过下面 birth_year 的打印后果,能够发现计算属性返回的是一个 ComputedRefImpl 实例,所以申明 ComputedRefImpl 类:

import { effect } from './effect.ts'class ComputedRefImpl {  public effect  constructor(getter, setter) {    // 默认 getter 执行时会依赖于 effect(计算属性默认是effect)    this.effect = effect(getter, {      lazy: true, // 默认初始化不执行    })  }}export function computed(getterOrOptions) {  let getter  let setter  if (isFunction(getterOrOptions)) {    getter = getterOrOptions    setter = () => console.log('computed not set value')  } else {    getter = getterOrOptions.get    setter = getterOrOptions.set  }  return new ComputedRefImpl(getter, setter)}

申明 ComputedRefImpl 类的公共属性和value属性的拜访器 get 和设置 set:

import { effect, track } from './effect.ts'class ComputedRefImpl {  public effect  public __v_isReadonly = true  public __v_isRef = true // 判断是否间接返回 value 值  public _dirty = true // 缓存数据  private _value  constructor(getter, public setter) {    // 默认 getter 执行时会依赖于 effect(计算属性默认是effect)    this.effect = effect(getter, {      lazy: true,    })  }  get value() {    // 当计算属性执行时,收集计算属性的 effect    this._value = this.effect()    return this._value  }  set value(newValue) {    this.setter(newValue)  }}

计算属性的依赖收集和scheduler

剖析

计算属性外面的响应式属性一旦被批改,须要告诉计算属性所在的effect函数做出更新操作:

如下,计算属性 birth_year 外面蕴含响应式属性 state.age:

const birth_year = computed(()=>{    return new Date().getFullYear() - state.age})

state.age做出批改操作:

setTimeout(() => {    state.age++}, 1000);

告诉birth_year所在的effect函数做出更新操作:

effect(() => {  app.innerHTML = `出生于 ${birth_year.value} 年`})

逻辑实现:

1、首先第一个 effect 办法开始执行,产生 activeEffect 并存储在 stackEffects 数组中:

const stackEffects = [activeEffect]

这里的 activeEffect 等于上面的办法:

effect1 = () => {  app.innerHTML = `出生于 ${birth_year.value} 年`}

也就是:

const stackEffects = [effect1]

2、接下来会进入计算属性 birth_year 的拜访器 value 办法,须要返回计算属性的执行后果:

get value(){    return new Date().getFullYear() - state.age}

为了记录以后计算属性所依赖的 effect 函数,批改ComputedRefImpl如下:

private _value;constructor(getter, public setter) {    this.effect = effect(getter, {lazy: true})}get value() {    this._value = this.effect()    return this._value}

effect 办法的执行存储了以后计算属性所在的 activeEffect,当初stackEffects 数组保留了两个 activeEffect:

const stackEffects = [effect1,effect2]

effect2实际上是计算属性的办法:

effect2 = ()=>{ return new Date().getFullYear() - state.age}
第一个 activeEffect 来自更新内容的 effect 函数,第二个 activeEffect 来自 computed

3、this.effect() 办法执行后,进入到state.age属性拜访器进行依赖收集,这里通过targetMap 映射表会将state.ageactiveEffect 进行关联:

targetMap = {state: { age: effect2 } }

4、关联后的 state.age 的产生更新操作,触发 effect2 函数的从新执行,上面是 effect1effect2 对应的函数:

effect1 = () => app.innerHTML = `出生于 ${birth_year.value} 年`;effect2 = ()=> new Date().getFullYear() - state.age;

5、计算属性冀望的是 state.age 的更新可能触发 effect1 的从新执行,所以在获取计算属性时,须要进行依赖收集:

get value() {    this._value = this.effect()+    track(this, 'value')    return this._value}

track 的执行让 targetMap 外面映射表变成了上面这样:

const targetMap = {  state: { age: effect2 },  ComputedRefImpl: { value: effect1 }}

6、为了让 state.age 的更新可能触发 effect1 的从新执行,批改构effect的options选项,新增scheduler 办法:

constructor(getter, public setter) {    this.effect = effect(getter, {        lazy: true, // lazy=true 默认不会执行        scheduler: () => {            trigger(this, TriggerType.set, 'value')        },    })}

批改effect.ts外面的 trigger 办法:

function trigger(){...  const run = (effects=[]) => {    effects.forEach((effect) => {        if(effect.options.scheduler){            effect.options.scheduler(effect)        }else{            effect();        }    })  } ...}

effectscheduler 属性办法时,执行 scheduler 办法,也就是 state.age 的批改会执行上面的逻辑:

trigger(this, TriggerType.set, 'value')

这个触发更新等于触发了 effect1 办法的从新执行:

effect1 = () => app.innerHTML = `出生于 ${birth_year.value} 年`;

执行过程

computed 的执行:

1、state.age 更新触发了 birth_year 的 computed effect 函数
2、computed effect 执行计算属性的 scheduler 办法
3、scheduler 触发了 birth_year.value 所在的 effect 函数更新

state.age => computed effect => scheduler => effect函数(birth_year.value)

残缺的 computed 办法:

import { effect, track, trigger, TriggerType } from './effect.ts'class ComputedRefImpl {  public effect  public __v_isReadonly = true  public __v_isRef = true // 判断是否间接返回 value 值  public _dirty = true // 缓存数据  private _value  constructor(getter, public setter) {    // 默认 getter 执行时会依赖于 effect(计算属性默认是effect)    this.effect = effect(getter, {      lazy: true,+      scheduler: () => {+        trigger(this, TriggerType.set, 'value')+      },    })  }  get value() {    // 当计算属性执行时,收集计算属性的 effect    this._value = this.effect()    // 收集计算属性外面的依赖+    track(this, 'value')    return this._value  }  set value(newValue) {    this.setter(newValue)  }}

依赖缓存

当批改跟计算属性没有关联的state.name时,能够看到birth_yeareffect也会被执行:

const { reactive, effect, computed } = VueReactivityconst state = reactive({ name: 'chenwl', age: 12 })const birth_year = computed(() => {+  console.log('computed execute')  return new Date().getFullYear() - state.age})effect(() => {  app.innerHTML = `${state.name} 出生于 ${birth_year.value} 年`})setTimeout(() => {+  state.name = 'change'}, 1000)

state.name产生扭转,控制台打印出 'computed execute'

state.name 所在的 effect 函数执行时,birth_year.value的属性拜访器也会被触发,收集依赖并执行计算属性的effect函数。

批改计算属性的value拜访器,依据后面申明的公共属性this._dirty,判断以后_dirty(脏值)是否为 true 来决定是否收集依赖和从新获取新值:

get value() {    if (this._dirty) {        this._value = this.effect()        track(this, 'value')        this._dirty = false    }    return this._value}

scheduler函数被执行时,阐明值被批改,须要从新设置_dirty:

constructor(getter, public setter) {    this.effect = effect(getter, {        lazy: true,        scheduler: () => {+            this._dirty = true            trigger(this, TriggerType.set, 'value')        },    })}

残缺的 computed.ts:

import { isFunction } from '../shared/index'import { effect, track, trigger, TriggerType } from './effect'class ComputedRefImpl {  public effect  public __v_isReadonly = true  public __v_isRef = true // 判断是否间接返回 value 值  public _dirty = true // 缓存数据  private _value  constructor(getter, public setter) {    this.effect = effect(getter, {      lazy: true, // lazy=true 默认不会执行      scheduler: () => {        this._dirty = true        trigger(this, TriggerType.set, 'value')      },    })  }  get value() {    if (this._dirty) {      this._value = this.effect()      track(this, 'value')      this._dirty = false    }    return this._value  }  set value(newValue) {    this.setter(newValue)  }}export function computed(getterOrOptions) {  let getter  let setter  if (isFunction(getterOrOptions)) {    getter = getterOrOptions    setter = () => console.log('computed not set value')  } else {    getter = getterOrOptions.get    setter = getterOrOptions.set  }  return new ComputedRefImpl(getter, setter)}

Ref

ref的实现,判断传入值是不是对象,对象用reactive包裹解决,获取值时收集依赖,设置值时触发更新

import { hasChanged, isObject } from "../shared/index";import { track, trigger, TriggerType } from "./effect";import { reactive } from "./reactive";const convert = (val) => isObject(val) ? reactive(val) : val;class RefImpl {    public readonly __v_isRef = true;    private _value;    constructor(private _rawValue){        this._value = convert(_rawValue)    }    get value(){        track(this,"value")        return this._value    }    set value(newValue){        if (hasChanged(newValue, this._rawValue)) {            this._rawValue = newValue;            this._value = convert(newValue);            trigger(this, TriggerType.set, "value");        }    }}export function ref(rawValue){    return new RefImpl(rawValue)}