Vue3的底层基于 响应式 - reactivityreactivity 是vue3的基础,是实现 mini-vue 的第一步。

在实现mini-vue 课程中教学的方式 - TDD
TDD :Test Driven Development 测试驱动开发 。

  1. 它要求在编写某个功能的代码之前先编写好测试代码
    2. 然后只编写使测试通过的功能代码
    3. 实现代码的重构 通过测试来推动整个开发的进行。

TDD 开发, 这有助于编写简洁可用和高质量的代码,并加速开发过程。

3.1 实现 reactive

, 对 reactive 的定义实现

  • 返回对象响应式副本
  • 基于 ES6 的 Proxy
  • 返回的响应式对象 不等于 原始对象

根据定义写 测试代码 , 在src/reactivity/test/ 创建 reactive.spec.ts

  1. // reactive 的实现
  2. describe('reactive', () => {
  3. // 1. 实现 reactive
  4. it('happy path', () => {
  5. // 原始对象
  6. let original = { foo: 1 }
  7. // 返回对象响应式副本
  8. let observed = reactive(original)
  9. // 初始对象 不等于 响应式对象
  10. expect(observed).not.toBe(original)
  11. // 响应式对象 和 原始对象 具有相同属性和属性值
  12. expect(observed.foo).toBe(1)
  13. })
  14. })

写逻辑代码让测试通过, 在src/reactivity 下创建 reactive.ts

  1. // reactive.ts
  2. export function reactive(raw) {
  3. // 返回一个Proxy 实例
  4. return new Proxy(raw, {
  5. // 对 对象属性进行 代理 get | set
  6. get(target, key) {
  7. let res = Reflect.get(target, key)
  8. // 这里执行依赖收集
  9. return res
  10. },
  11. set(target, key, value) {
  12. let res = Reflect.set(target, key, value)
  13. // 触发依赖
  14. return res
  15. }
  16. })
  17. }

执行 pnpm test reactive 检查 测试是否全部通过

3.2 实现 effect

effect 的实现

  • 接收一个 函数 fn 作为参数 , 当执行 effect 是会执行这个 fn
  • 这个 fn 内部使用了响应式对象数据,当响应式数据发生变化时, effect 会再次执行 fn

写 effect 的测试代码 src/reactivity/test/effect.spec.ts

  1. describe('effect', () => {
  2. it('happy path', () => {
  3. // 定义响应式对象
  4. const user = reactive({ age: 10 })
  5. let nextAge
  6. // effect 接收一个fn 做参数, 初始时会调用这个fn
  7. effect(() => {
  8. nextAge = user.age + 1
  9. })
  10. // 当执行 effect 完后 nextAge 变为 11
  11. expect(nextAge).toBe(11)
  12. // 当更新响应式对象, effect 会再次调用 fn
  13. user.age++
  14. // 当执行 effect 完后 nextAge 变为 12
  15. expect(nextAge).toBe(12)
  16. })
  17. })

实现 effect 的逻辑, src/reactivity/effect.ts

初始时候, 导出 effect 方法,并且使用 ReactiveEffect 这个类 作为容器,在执行 effect 时候, 创建 ReactiveEffect实例 _effect,把 fn 保存到 容器中。 在容器上定义run方法 ,调用 run()相当于执行fn

  1. // 全局变量
  2. let activeEffect
  3. // 定义存储依赖的容器 ReactiveEffect
  4. class ReactiveEffect {
  5. private _fn: any;
  6. constructor(fn) {
  7. this._fn = fn
  8. }
  9. // 定义 run 方法,调用 run()相当于执行fn
  10. run() {
  11. this._fn()
  12. }
  13. }
  14. export function effect(fn) {
  15. const _effect = new ReactiveEffect(fn)
  16. // 执行它的run方法
  17. _effect.run()
  18. }

这样就实现了 effect 一开始就调用了 fn() , 在执行 fn()时,读取到响应式对象的 get | set 属性, 可以在调用这些属性时,在返回的 Proxy 实例中进行 依赖收集 getter或 触发依赖 setter

  1. // reactive.ts
  2. export function reactive(raw) {
  3. return new Proxy(raw, {
  4. get(target, key) {
  5. let res = Reflect.get(target, key)
  6. // 这里执行依赖收集
  7. trick(target, key)
  8. return res
  9. },
  10. set(target, key, value) {
  11. let res = Reflect.set(target, key, value)
  12. // 触发依赖
  13. trigger(target, key, value)
  14. return res
  15. }
  16. })
  17. }

effect.ts中定义 trick | trigger 函数 , 在实现依赖收集和触发依赖, 需要使用全局变量 activeEffect 保存当前正在执行的实例, 使用 targetMap 保存运行中的依赖

  1. // 全局变量
  2. let activeEffect: ReactiveEffect // 保存执行的实例
  3. const targetMap = new Map() // 保存运行中的依赖
  4. // 定义存储依赖的容器 ReactiveEffect
  5. class ReactiveEffect {
  6. /* 其他代码 */
  7. run() {
  8. // 在调用 run时, 赋值 activeEffect 为当前 ReactiveEffect
  9. activeEffect = this
  10. // 执行 fn() 就是 effect 传入过来的那个函数 ---- 依赖
  11. this._fn()
  12. }
  13. }
  14. // 依赖收集
  15. export function trick(target, key) {
  16. // 如果没有activeEffect这个实例,不需要进行依赖收集
  17. if (!activeEffect) return
  18. // 按照这个方式去设置 dep 的存储:targetMap -> depsMap -> dep -> ReactiveEffect
  19. let depsMap = targetMap.get(target)
  20. if (!depsMap) { // 如果没有depsMap,进行初始化
  21. depsMap = new Map()
  22. targetMap.set(target, depsMap) // 把depsMap 添加到 targetMap
  23. }
  24. // 判断是否有 dep
  25. let dep = depsMap.get(key)
  26. if (!dep) {
  27. dep = new Set() // 定义 dep, 使用 Set() 收集 ReactiveEffect
  28. depsMap.set(key, dep)
  29. }
  30. // 若 dep 中包括当前正在执行的 ReactiveEffect 类的实例则直接返回, 不用进行收集了
  31. if (dep.has(activeEffect)) return
  32. // ReactiveEffect 收集在 dep 中
  33. dep.add(activeEffect)
  34. }
  35. // 触发依赖
  36. // 执行依赖 run() 方法
  37. export function trigger(target, key, value) {
  38. // targetMap -> depsMap -> dep -> ReactiveEffect
  39. // 1. 先取出 depsMap
  40. let depsMap = targetMap.get(target)
  41. // 2. 再取出 dep
  42. let dep = depsMap.get(key)
  43. // 3. 遍历 dep, 执行 触发依赖 run() 方法
  44. for (const effect of dep) {
  45. effect.run()
  46. }
  47. }

执行 pnpm test effect 运行测试,测试通过就完成 effect的基本实现

3.3 实现 effect —— 返回 runner

  • 执行 effect 会返回一个 函数, 用一个runner 的变量接收该函数
  • 当调用 runner 会执行 effect内的 fn
  • 同时 fn()会有一个返回值

    effect.spec.ts 中添加 runner 的测试

    1. // 2. 实现 runner
    2. it('should return function when call effect', () => {
    3. // 2.1 effect执行会返回 runner() 函数,
    4. // 2.2 执行runner() 会执行 effect内部的 fn, runner 也就是 fn
    5. // 2.3 执行 runner() 会有一个返回值, 这个返回值就是 fn() 内部的定义的返回值
    6. let foo = 10
    7. // effect 会返回出一个runner()函数
    8. const runner = effect(() => {
    9. foo++
    10. return "foo"
    11. })
    12. // effect 初始时 fn 会调用一次
    13. expect(foo).toBe(11)
    14. // 调用 runner() 会执行 fn()
    15. const run = runner()
    16. expect(foo).toBe(12)
    17. // runner() 返回的值是 fn() 的返回值
    18. expect(run).toBe("foo")
    19. })

effect.ts实现 返回 runnerfn的返回值

  1. class ReactiveEffect {
  2. run() {
  3. activeEffect = this
  4. // 设置 fn 的返回值
  5. return this._fn()
  6. }
  7. }
  8. export function effect(fn) {
  9. const _effect = new ReactiveEffect(fn)
  10. _effect.run()
  11. // 定义 返回的 runner -> runner 就是 fn()
  12. const runner: any = _effect.run.bind(_effect)
  13. return runner
  14. }

执行 pnpm test effect 运行测试,测试通过就完成 runner的基本实现

3.4 实现 effect —— 接收 scheduler

  • scheduler 是一个函数,是 effect 的第二个参数
  • scheduler 一开始不会调用,也就是 初始化 effect () 不会调用 scheduler()
  • 当修改响应式对象值时, 会调用 scheduler() ,而且 fn() 不被执行
  • 当执行 runner , 会再次执行 fn

    effect.spec.ts 中添加 scheduler 的测试

    1. // 3. 实现 scheduler
    2. it('scheduler', () => {
    3. // 1. 通过effect 的第二个参数给定了一个 scheduler 的 fn
    4. // 2. 当 effect 第一次执行的时候, 还会执行fn
    5. // 3. 当 响应式对象 set update 时, 不会执行 fn , 而是执行 scheduler
    6. // 4. 如果说当执行 runner 的时候, 会再次执行 fn
    7. let dummy
    8. const obj = reactive({ foo: 1 })
    9. let run
    10. const scheduler = jest.fn(() => {
    11. run = runner
    12. })
    13. // scheduler 作为 effect 的第二个参数
    14. const runner = effect(() => {
    15. dummy = obj.foo
    16. }, { scheduler })
    17. // effect 初始化时调用 fn
    18. expect(dummy).toBe(1)
    19. // scheduler 初始化时候没有被调用
    20. expect(scheduler).not.toHaveBeenCalled()
    21. // update -> set -> 调用 scheduler
    22. obj.foo = 2
    23. // scheduler 被调用
    24. expect(scheduler).toHaveBeenCalledTimes(1)
    25. // 当 runner 调用时, 才会执行 fn
    26. run()
    27. expect(dummy).toBe(2)
    28. })

effect.ts中实现 scheduler() 的逻辑代码

  1. // 触发依赖
  2. export function trigger(target, key, value) {
  3. // 取出依赖
  4. // targetMap -> depsMap -> dep -> ReactiveEffect
  5. let depsMap = targetMap.get(target)
  6. let dep = depsMap.get(key)
  7. // 循环执行 run()
  8. for (const effect of dep) {
  9. // 这里判断是否有 scheduler 参数, 如果没有则执行run() 方法
  10. if (effect.scheduler) {
  11. effect.scheduler()
  12. } else {
  13. effect.run()
  14. }
  15. }
  16. }
  17. export function effect(fn, options: any = {}) {
  18. // options effect 的第二个参数, 它是可选的
  19. const _effect = new ReactiveEffect(fn, options.scheduler)
  20. /* 其他代码 */
  21. }

执行 pnpm test effect 运行测试,测试通过就完成 scheduler的基本实现

3.5 实现 effect —— stop

  • stop() 是一个函数, 在 effect 内部实现
  • stop() 接收 runner 作为参数, 当执行stop() 后,更新响应式对象值时,不会发生改变
  • 但是 直接调用 runner() 响应式对象还是会发生变化

    effect.spec.ts 中添加 stop的测试

    1. // 4. 实现 stop
    2. it('stop', () => {
    3. // 1. stop 是 effect 内部实现的函数
    4. // 2. stop() 接收 runner 参数, 调用后 响应式对象值不会再发生改变
    5. // 3. 但是直接调用 runner() 还是会让响应式对象发生改变
    6. // 定义响应式
    7. let dummy
    8. let obj = reactive({ props: 1 })
    9. const runner = effect(() => {
    10. dummy = obj.props
    11. })
    12. // update -> set
    13. obj.props++
    14. expect(dummy).toBe(2)
    15. // 调用 stop()
    16. stop(runner)
    17. obj.props = 3
    18. expect(dummy).toBe(2)
    19. // 调用 runner() 触发更新
    20. runner()
    21. expect(dummy).toBe(3)
    22. })

effect.ts 实现 stop 的逻辑

  1. // 定义存储依赖的容器 ReactiveEffect
  2. class ReactiveEffect {
  3. private _fn: any
  4. // 用于保存 dep, 方便拿到正在执行的 ReactiveEffect
  5. deps: any[] = []
  6. // 判断 stop() 是否被调用过,如果被调用过,则不需要再次调用 设置 active = false
  7. active: boolean = true
  8. /* 其他代码 */
  9. stop() {
  10. // 实现stop, 只需要把 dep 进行清空
  11. // 问题? 如果通过当前的 ReactiveEffect实例对象 找到 dep 对象
  12. // 实现: 在 ReactiveEffect 定义 deps 的数组,在执行get() 把dep添加到数组中
  13. // 使用 cleanupEffect 函数 抽离清空的逻辑
  14. // 考虑到性能问题,用户可能会多次调用 stop 方法,所以这里需要做一个判断
  15. // 使用 active 判断是否已经被清空
  16. if (this.active) {
  17. // 清空dep的逻辑
  18. cleanupEffect(this)
  19. // 把 active 的状态设置为 false
  20. this.active = false
  21. }
  22. }
  23. }
  24. // 执行清空 deps -> dep
  25. function cleanupEffect(effect) {
  26. effect.deps.forEach(dep => dep.delete(effect))
  27. }
  28. export function trick(target, key) {
  29. if (!activeEffect) return
  30. /* 其他代码 */
  31. // ReactiveEffect 存储在 dep 中
  32. dep.add(activeEffect)
  33. // 添加 dep 到 deps 这个实例的数组中
  34. activeEffect.deps.push(dep)
  35. }
  36. export function effect(fn, options: any = {}) {
  37. /* 其他代码 */
  38. // 定义 返回的 runner -> runner 就是 fn()
  39. const runner: any = _effect.run.bind(_effect)
  40. // 把 effect 实例保存在 runner 上, 这样stop用到runner, 才能拿到正在执行的ReactiveEffect
  41. runner.effect = _effect
  42. return runner
  43. }
  44. export function stop(runner) {
  45. // 在 ReactiveEffect 实习 stop 逻辑
  46. runner.effect.stop()
  47. }

执行 pnpm test effect 运行测试,测试通过就完成 stop的基本实现

3.6 实现 effect —— onStop

  • onStop是一个函数
  • onStop 是 effect 的第二个参数
  • 当执行完 stop() 后,onStop() 会被执行,也就是说 onStop 是 stop 的一个回调函数

effect.spec.ts

  1. // 5. 实现 onStop
  2. it('onStop', () => {
  3. // 1. onStop() 是 effect 的第二个参数, onStop() 是一个函数
  4. // 2. 当 stop() 执行完后, onStop() 会被执行,也就是 stop() 的回调
  5. // 定义响应式
  6. let dummy
  7. let obj = reactive({ foo: 1 })
  8. // 定义 onStop()
  9. const onStop = jest.fn()
  10. const runner = effect(
  11. () => { dummy = obj.foo },
  12. { onStop } // onStop() 第二个参数
  13. )
  14. // 当调用stop()时, onStop() 会被执行
  15. stop(runner)
  16. expect(onStop).toHaveBeenCalledTimes(1)
  17. })

effect.ts

  1. class ReactiveEffect {
  2. // 定义onStop, 用于保存 onStop 方法
  3. onStop?: () => void
  4. // ...
  5. stop() {
  6. if (this.active) {
  7. // 清空dep的逻辑
  8. cleanupEffect(this)
  9. // 判断是否有 onStop 回调
  10. if (this.onStop) {
  11. // 执行 onStop 回调
  12. this.onStop()
  13. }
  14. // 把 active 的状态设置为 false
  15. this.active = false
  16. }
  17. }
  18. }
  19. export function effect(fn, options: any = {}) {
  20. // options effect 的第二个参数, 它是可选的
  21. const _effect = new ReactiveEffect(fn, options.scheduler)
  22. // onStop 的逻辑
  23. // 使用 Object.assign(effect, options) 挂载options
  24. // Object.assign(_effect, options)
  25. // 对 Object.assign() 进行封装语义化 -> 把Object.assign抽离出去,改个名字
  26. extend(_effect, options)
  27. // ...
  28. }

Objest.assign()方法抽离为 公共的方法 extend
src/shared/index.ts

  1. export const extend = Object.assing

执行 pnpm test effect 运行测试,测试通过就完成 onStop的基本实现

优化 stop

在 测试中stop 的 obj.props = 3, 如果把obj.props++会出现问题。
因为执行 obj.props++ = obj.props = obj.props + 1 即执行了 getter 又执行了 setter , 而在之前 stop() 时候已经清除掉了 上一个getter 收集的依赖 。
所以执行obj.props++ 这里执行 gette 经历初始化,然后执行 setter 时候修改数据 相当于 : 1 + 1 ,已经收集不到依赖

解决问题:

  • 只需要在它收集依赖时候 判断它是不是执行过 stop()的操作, 如果是 就不要进行依赖的收集 , 直接执行 fn
  • 如果不是 stop() 执行 依赖收集 ```typescript // effect.spec.ts

// 4. 实现 stop it(‘stop’, () => { let dummy let obj = reactive({ props: 1 }) const runner = effect(() => { dummy = obj.props }) obj.props = 2 expect(dummy).toBe(2) // 调用 stop() stop(runner) // obj.props = 3 obj.props++ // 修改时 expect(dummy).toBe(2) // 调用 runner() 触发更新 runner() expect(dummy).toBe(3) // bug : 这里 dummy 为 2 })

  1. 优化后 effect<br />逻辑:
  2. - 使用 shouldTrack 判断是否进行依赖收集 , run() 的时候进行给 shouldTrack 赋值状态
  3. - 然后在 `trick()` 执行依赖收集时候, 判断 shouldTrack 的状态 就行
  4. ```typescript
  5. // shouldTrack 判断是否执行依赖收集, 在 run() 时候进行调用
  6. let shouldTrack
  7. class ReactiveEffect {
  8. run() {
  9. // 因为执行 run() 方法会依赖收集,
  10. // 执行 stop() 不需要进行依赖的收集, 可以直接在run 方法这里判断, 做操作, active 判断 stop() 是否被调用
  11. if (!this.active) {
  12. // 返回 fn
  13. return this._fn()
  14. }
  15. // 设置 shouldTrack 变量为 true, 表示进行依赖收集
  16. shouldTrack = true
  17. // 赋值 activeEffect 为当前 ReactiveEffect
  18. activeEffect = this
  19. // 执行 fn() 就是 effect 传入过来的那个函数 ---- 依赖
  20. // this._fn()
  21. let result = this._fn()
  22. // reset 复位: 设置 shouldTrack 的状态 为 false , 关闭依赖收集 -> 在 track 执行 收集依赖前把他给返回
  23. shouldTrack = false
  24. return result
  25. }
  26. }
  27. function cleanupEffect(effect) {
  28. effect.deps.forEach(dep => dep.delete(effect))
  29. // 优化
  30. effect.deps.length = 0
  31. }
  32. export function trick(target, key) {
  33. if (!activeEffect) return // 如果 activeEffect 为 undefined 直接返回
  34. if (!shouldTrack) return // 如果 shouldTrack 为 true 直接返回 , 不会执行以下代码 依赖收集
  35. //...
  36. // 如果 dep这个Set() 中 有 activeEffect , 直接 true
  37. // 因为 dep 已经有 相应的依赖了, 不要重复去收集
  38. if (dep.has(activeEffect)) return
  39. // ReactiveEffect 存储在 dep 中
  40. dep.add(activeEffect)
  41. // 添加 dep 到 deps 这个实例的数组中
  42. activeEffect.deps.push(dep)
  43. }

执行 pnpm test 测试全部的代码案例 。

3.7 实现 readonly

创建 readonly.spec.ts的测试文件

  • readonly 表示只读, 表明被 它代理过的 不可以进行 set 操作
  • 在尝试 修改 readonly 返回的 代理对象, 会返回一个错误 console.warn

reactivity/test/readonly.ts 测试 readonly

  1. import { readonly } from "../reactive"
  2. describe('readonly', () => {
  3. // 1. 实现 readonly
  4. it('happy path', () => {
  5. // readonly 只读
  6. // 1. 表明着它不可以被 set
  7. // 2. 在尝试修改它时候会抛出一个错误, 调用 set 时候 抛出一个错误
  8. const original = { foo: 1, bar: { baz: 2 } }
  9. const observed = readonly(original)
  10. // readonly 返回对象 与 原始对象 不同
  11. expect(observed).not.toBe(original)
  12. // readonly 返回对象 与 原始对象 的 属性值相同
  13. expect(observed.foo).toBe(1)
  14. })
  15. // 2. 当执行 set 时候抛出一个错误
  16. it('warn then call set', () => {
  17. // update
  18. console.warn = jest.fn()
  19. const user = readonly({ age: 19 })
  20. user.age = 20
  21. expect(console.warn).toBeCalled()
  22. })
  23. })

reactive.ts

  1. // 实现 readonly 的逻辑
  2. export function readonly(raw) {
  3. return new Proxy(raw, {
  4. get(target, key) {
  5. return Reflect.get(target, key)
  6. },
  7. set(target, key, value) {
  8. console.warn(`key: ${String(key)} set 失败, 因为 target 是 readonly`, target)
  9. return true
  10. }
  11. })
  12. }

运行测试 : pnpm test reactive , 测试通过就完成 readonly的基本实现

优化 reactive

在实现 reactivereadonly 时具有许多相同的逻辑, 需要把这些相同的逻辑进行重构, 抽离重复代码, 提高可读性

创建 reactivity/baseHandlers.tsreactive.ts 中实现数据代理的getter | setter 逻辑抽离, 并使用全局变量进行缓存

  1. // 使用全局变量缓存方法,防止重复调用
  2. const get = createGetter()
  3. const set = createSetter()
  4. const readonlyGet = createGetter(true)
  5. // 创建 getter 的函数
  6. // 参数设置 是否为 readonly 默认为false
  7. function createGetter(isReadonly: boolean = false) {
  8. // 返回一个函数
  9. return function (target, key) {
  10. let res = Reflect.get(target, key)
  11. // 如果不是只读的
  12. if (!isReadonly) {
  13. // 这里执行依赖收集
  14. trick(target, key)
  15. }
  16. return res
  17. }
  18. }
  19. // 创建 setter 的函数
  20. function createSetter() {
  21. return function (target, key, value) {
  22. let res = Reflect.set(target, key, value)
  23. // 触发依赖
  24. trigger(target, key, value)
  25. return res
  26. }
  27. }
  28. // 导出 reactive 对应的 handler
  29. export const mutableHandlers = {
  30. get,
  31. set
  32. }
  33. // 导出 readonly 对应的 handler
  34. export const readonlyHanders = {
  35. get: readonlyGet,
  36. set(target, key, value) {
  37. console.warn(`key: ${String(key)} set 失败, 因为 target 是 readonly`, target)
  38. return true
  39. }
  40. }

reactive.ts 中对 reactive | readonly的实现进行优化 , 抽离重复代码

  1. import { mutableHandlers, readonlyHanders } from "./baseHandlers"
  2. function createReactiveObject(raw, baseHandlers) {
  3. // 返回创建 new Proxy 的实例
  4. return new Proxy(raw, baseHandlers)
  5. }
  6. export function reactive(raw) {
  7. return createReactiveObject(raw, mutableHandlers)
  8. }
  9. export function readonly(raw) {
  10. return createReactiveObject(raw, readonlyHanders)
  11. }

3.8 实现 isReadonly & isReactive & isProxy

这三个API都是 Vue3 中响应式具有的API

3.8.1 isReactive

  • 检查对象是否是由 reactive创建的响应式代理。

reactive.spec.ts写测试内容

  1. // 2. isReactive 判断是否是响应式对象
  2. it('isReactive', () => {
  3. let original = { foo: 1 }
  4. let observable = reactive(original)
  5. expect(isReactive(observable)).toBe(true)
  6. expect(isReactive(original)).toBe(false)
  7. })

reactive.ts实现 isReactive的逻辑

  1. export function isReactive(value) {
  2. // 实现逻辑:通过读取这个对象的key值,因为读取到key,会触发getter(),
  3. // 然后再getter中去判断是否是reactive
  4. return !!value['__v_isReactive']
  5. }

在触发getter 前对响应式进行判断, baseHandler.ts 中的代码

  1. // 创建 getter
  2. function createGetter(isReadonly: boolean = false) {
  3. return function (target, key) {
  4. // 这里判断 target, 是 reactive 还是 readonly
  5. // 根据 key 来判断
  6. if (key === '__v_isReactive') {
  7. return !isReadonly
  8. }
  9. // ....
  10. }
  11. }

3.8.2 isReadonly

  • 检查对象是否是由 readonly 创建的只读代理。

isReadonly 的逻辑基本和 isReactive 的实现是基本一样的
readonly.spec.ts的测试逻辑

  1. // 3. 判断 isReadonly 判断是否是只读对象
  2. it('isReadonly', () => {
  3. let original = { foo: 1 }
  4. let observable = readonly(original)
  5. expect(isReadonly(observable)).toBe(true)
  6. expect(isReadonly(original)).toBe(false)
  7. })

reactive.ts

  1. export function isReadonly(value) {
  2. // 实现逻辑:通过读取这个对象的key值,因为读取到key,会触发getter(),然后再getter中去判断是否是reactive
  3. return !!value['__v_isReadonly']
  4. }

在触发getter 前对响应式进行判断, baseHandler.ts 中的代码

  1. // 创建 getter
  2. function createGetter(isReadonly: boolean = false) {
  3. return function (target, key) {
  4. // 这里判断 target, 是 reactive 还是 readonly
  5. // 根据 key 来判断
  6. if (key === '__v_isReactive') {
  7. return !isReadonly
  8. } else if (key === '__v_isReadonly') {
  9. return isReadonly
  10. }
  11. // ...
  12. }
  13. }

3.8.3 isProxy

测试逻辑

  1. // readonly.spec.ts
  2. // 4. isProxy
  3. // isProxy 判断对象是否 reactive | readonly
  4. it('nested reactive', () => {
  5. const original = { foo: 1, bar: { baz: 2 }, array: [{ bar: 2 }] }
  6. const observed = readonly(original)
  7. expect(isProxy(observed)).toBe(true)
  8. })
  9. // reactive.spec.ts
  10. // 3. isProxy
  11. // isProxy 判断对象是否 reactive | readonly
  12. it('nested reactive', () => {
  13. const original = { foo: 1, bar: { baz: 2 }, array: [{ bar: 2 }] }
  14. const observed = reactive(original)
  15. expect(isProxy(observed)).toBe(true)
  16. })

reactive.ts 实现 isProxy的逻辑

  1. // isProxy 判断是否是代理对象
  2. export function isProxy(value) {
  3. return isReactive(value) || isReadonly(value)
  4. }

运行测试: pnpm test ,测试没有出现问题说明 基本的响应式API已经实现

3.8.4 重构优化

因为在 isReadonly | isReactive 中实现使用到了 key的字符串 ,直接使用和在getter中也是直接 === 判断,这样的代码不够简洁。 可以使用 枚举方式定义字符串

优化 reactive.ts

  1. export const enum ReactiveFlags {
  2. IS_REACTIVE = '__v_isReactive',
  3. IS_READONLY = '__v_isReadonly',
  4. }
  5. export function isReactive(value) {
  6. // 实行逻辑:通过读取这个对象的key值,因为读取到key,会触发getter(),然后再getter中去判断是否是reactive
  7. return !!value[ReactiveFlags.IS_REACTIVE]
  8. }
  9. export function isReadonly(value) {
  10. // 实行逻辑:通过读取这个对象的key值,因为读取到key,会触发getter(),然后再getter中去判断是否是reactive
  11. return !!value[ReactiveFlags.IS_READONLY]
  12. }

baseHandler.ts使用 枚举

  1. // 创建 getter
  2. function createGetter(isReadonly: boolean = false) {
  3. return function (target, key) {
  4. if (key === ReactiveFlags.IS_REACTIVE) {
  5. return !isReadonly
  6. } else if (key === ReactiveFlags.IS_READONLY) {
  7. return isReadonly
  8. }
  9. // ...
  10. }
  11. }

3.9 实现 readonly & reactive 嵌套对象转换

  • 但需要进行代理的源对象是 嵌套形式 , 需要把更深层次的对象也变为代理的对象
  • 实现逻辑: 在 执行getter() 时,判断需要反射出去的值 let res = Reflect.get(target, key) , 如果它是一个对象,那就继续代理 。

测试的逻辑 : readonly.spec.ts | reactive.spec.ts

  1. // readonly.spec.ts
  2. // 5. 嵌套的 readonly
  3. it('nested readonly', () => {
  4. const original = { foo: 1, bar: { baz: 2 }, array: [{ bar: 2 }] }
  5. const observed = readonly(original)
  6. // 1. 嵌套的bar 也应该是一个 redonly 对象
  7. expect(isReadonly(observed.bar)).toBe(true)
  8. // 2. 嵌套的array 也应该是一个 redonly 对象
  9. expect(isReadonly(observed.array)).toBe(true)
  10. // 3. 嵌套的array 中的元素也应该是一个 redonly 对象
  11. expect(isReadonly(observed.array[0])).toBe(true)
  12. })
  13. // reactive.spec.ts
  14. // 4. 嵌套的 reactive
  15. it('nested reactive', () => {
  16. const original = { foo: 1, bar: { baz: 2 }, array: [{ bar: 2 }] }
  17. const observed = reactive(original)
  18. // 1. 嵌套的bar 也应该是一个 redonly 对象
  19. expect(isReactive(observed.bar)).toBe(true)
  20. // 2. 嵌套的array 也应该是一个 redonly 对象
  21. expect(isReactive(observed.array)).toBe(true)
  22. // 3. 嵌套的array 中的元素也应该是一个 redonly 对象
  23. expect(isReactive(observed.array[0])).toBe(true)
  24. })

实现的逻辑: 在 执行getter() 时,判断需要反射出去的值 let res = Reflect.get(target, key) , 如果它是一个对象,那就继续代理 。
baseHandler.ts

  1. // 创建 getter
  2. function createGetter(isReadonly: boolean = false) {
  3. return function (target, key) {
  4. // ...
  5. let res = Reflect.get(target, key)
  6. // if(res !== null && typeof res === 'object') -> 抽离为isObject
  7. // 这里判断 res 是不是一个对象,如果是执行嵌套
  8. if (isObject(res)) {
  9. return isReadonly ? readonly(res) : reactive(res)
  10. }
  11. // ...
  12. }
  13. }

shared/index.ts, 定义抽离 对 res 的判断

  1. export function isObject (value) {
  2. return value !== null && typeof value === 'object'
  3. }

运行测试 : pnpm test , 通过测试说明实现了 嵌套的逻辑

3.10 实现 shallowReactive & shallowReadonly

在vue3响应式API中实现了
shallowReactive & shallowReadonly 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换

创建 shallowReadonly.spec.ts | shallowReactive.spec.ts 测试文件

  1. // shallowReadonly.spec.ts
  2. // shallowReadonly
  3. describe('shallowReadonly', () => {
  4. // 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换
  5. it('should not make not-reactive properties reactive', () => {
  6. const props = shallowReadonly({ n: { foo: 1 } })
  7. // 1. 测试: props 是一个readonly对象
  8. expect(isReadonly(props)).toBe(true)
  9. // 2. 测试: props.n 不是一个 readonly 对象
  10. expect(isReadonly(props.n)).toBe(false)
  11. })
  12. })
  13. // shallowReactive.spec.ts
  14. // shallowReactive
  15. describe('shallowReactive', () => {
  16. // 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换
  17. it('should not make not-reactive properties reactive', () => {
  18. const props = shallowReactive({ n: { foo: 1 } })
  19. // 1. 测试: props 是一个 reactive 对象
  20. expect(isReactive(props)).toBe(true)
  21. // 2. 测试: props.n 不是一个 reactive对象 对象
  22. expect(isReactive(props.n)).toBe(false)
  23. })
  24. })

实现逻辑: 基本和 readonly | reactive 一样,改变它们调用的 getter() 返回值。 不让继续代理

  • reactive.ts 创建对应的方法 ```typescript export function shallowReadonly(raw) { return createReactiveObject(raw, shallowReadonlyHandlers) }

export function shallowReactive(raw) { return createReactiveObject(raw, shallowReactiveHandlers) }

  1. - `baseHandlers.ts` 中定义相应返回的 getter 指向
  2. ```typescript
  3. const shallowReadonlyGet = createGetter(true, true)
  4. const shallowReactiveGet = createGetter(false, true)
  5. // 创建 getter
  6. // 添加了判断是否是 shallow 类型的方法
  7. function createGetter(isReadonly: boolean = false, isShallow: boolean = false) {
  8. return function (target, key) {
  9. // ...
  10. let res = Reflect.get(target, key)
  11. // 这里添加判断, 如果调用get() 的是 ShallowReadonly 方法, 直接返回 res
  12. if (isShallow) {
  13. return res
  14. }
  15. // ...
  16. }
  17. }
  18. // 这里使用到了 extend() 方法,用于合并 指向的 setter()
  19. export const shallowReadonlyHandlers = extend({}, readonlyHanders, {
  20. get: shallowReadonlyGet
  21. })
  22. export const shallowReactiveHandlers = extend({}, mutableHandlers, {
  23. get: shallowReactiveGet
  24. })

运行测试: pnpm test , 通过全部测试,说明 shallowReadonly | shallowReactive 的功能实现

3.11 实现 ref

, 对 ref 的定义
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的 property .value。

  1. const count = ref(0)
  2. console.log(count.value) // 0
  3. count.value++
  4. console.log(count.value) // 1

最基础的ref 的实现:

  • ref 是响应式的,对所有 .value 的操作会追踪。 触发自己的 getter | setter
  • 可以具有一个 .value 获取属性值

测试用例 test/ref.spec.ts

  1. describe('happy path', () => {
  2. // 1. 实现 ref
  3. it('ref', () => {
  4. const a = ref(1)
  5. // 测试1. 通过访问 .value 获取属性值
  6. expect(a.value).toBe(1)
  7. })
  8. // 2. 使用 响应式的 ref
  9. it('should be reactive', () => {
  10. const a = ref(1)
  11. let dummy
  12. let calls: number = 0
  13. effect(() => {
  14. calls++
  15. dummy = a.value
  16. })
  17. // 执行测试
  18. expect(calls).toBe(1) // effect 应该被调用一次
  19. expect(dummy).toBe(1) // dummy 应该为 1
  20. // 执行更新逻辑
  21. a.value = 2
  22. expect(calls).toBe(2) // fn 会被调用一次, 执行依赖
  23. expect(dummy).toBe(2) //
  24. // 当再次更新为相同的值时,不用重新调用 effect
  25. a.value = 2
  26. expect(calls).toBe(2)
  27. expect(dummy).toBe(2)
  28. })
  29. // 3. 当 ref() 的值是一个对象时候
  30. it('should make nested properties reactive', () => {
  31. // ref({}) 参数一个对象
  32. const a = ref({
  33. count: 1
  34. })
  35. let dummy
  36. effect(() => {
  37. // 通过 .value 拿到 count
  38. dummy = a.value.count
  39. })
  40. expect(dummy).toBe(1)
  41. // 更新 count
  42. a.value.count = 2
  43. expect(dummy).toBe(2)
  44. })
  45. })

实现 ref : reactivity/ref.ts

  1. 实现最基础的 ref ```typescript // 创建 RefImpl 保存 ref 的类, 并通过 getter | setter 代理.value class RefImpl { private _value: any constructor(value) { tihs._value = value } get value() { return this._value }

    // TODO: 触发依赖 set value(newValue: any) { this._value = newValue return true } }

// ref export function ref(ref) { // 返回一个 new RefImpl 的实例 return new RefImpl(ref) }

  1. 2. 完善 ref
  2. 1. 实现 收集依赖 -> getter
  3. 2. 实现 执行依赖 -> setter
  4. 因为之前实现 effect 时,封装过依赖收集 执行依赖 的方法 `taick() | trigger()`抽离依赖收集 | 执行依赖 的逻辑代码,达到 ref | reactive 都能使用依赖收集 | 执行依赖 <br />`effect.ts`
  5. ```typescript
  6. export function track(target, key) {
  7. // ...
  8. // 抽离 依赖收集的逻辑 ,传入 dep 参数, 这样 ref 传入 定义的ref 就能使用依赖收集了
  9. // trackEffects
  10. trackEffects(dep)
  11. }
  12. // 把执行依赖收集的逻辑抽离,同样方便 ref 使用
  13. export function trackEffects(dep) {
  14. if (dep.has(activeEffect)) return
  15. dep.add(activeEffect)
  16. activeEffect.deps.push(dep)
  17. }
  18. export function trigger(target, key) {
  19. // ...
  20. triggerEffects(dep) // 抽离出触发依赖的方法
  21. }
  22. // 收集 执行依赖的逻辑抽离,同样方便 ref 使用
  23. export function triggerEffects(dep) {
  24. for (let effect of dep) {
  25. // 当触发依赖时,这里判断是否具有 scheduler,如果有就执行 scheduler,否则就执行 effect.run()
  26. if (effect.scheduler) {
  27. effect.scheduler()
  28. } else {
  29. effect.run()
  30. }
  31. }
  32. }
  33. export function isTracking() {}

ref.ts

  1. // 创建 RefImpl 保存 ref 的类
  2. class RefImpl {
  3. private _value: any
  4. public dep
  5. public _rawValue: any
  6. public isRef = '_v_isRef'
  7. constructor(value) {
  8. this._rawValue = value
  9. // 如果说 传过来的value 是一个对象的话,使用 reactive() 进行一个代理
  10. // this._value = isObject(value) ? reactive(value) : value // 接收的值
  11. // 重构
  12. this._value = convert(value)
  13. // 实例化 dep 的值
  14. this.dep = new Set()
  15. }
  16. // 通过 .value 访问 ref 的值
  17. // TODO: 执行依赖收集
  18. get value() {
  19. // 重构: 抽离收集依赖的方法: trackRefValue()
  20. trackRefValue(this)
  21. return this._value
  22. }
  23. // TODO: 触发依赖
  24. set value(newValue) {
  25. // 判断修改的值 与 之前的值是否相同,如果相同,则不执行触发依赖
  26. // 重构: hasChanged 判断是否发生改变
  27. // 问题2: 当修改的值是一个对象的话,对比会出现问题 object 的 对比; newValue 是一个 原始对象,而 _value 是一个代理对象
  28. // 解决: 使用 _rawValue 保存 ref 的原始值
  29. if (hasChanged(newValue, this._rawValue)) {
  30. // 转换
  31. this._rawValue = newValue // 赋值为新的值
  32. // this._value 就等于代理的对象
  33. this._value = convert(newValue)
  34. // // 先修改值后,再进行触发依赖
  35. // this._value = newValue
  36. // 与收集依赖一样的逻辑,在 ReactiveEffect 已经定义好收集依赖的逻辑, 调用就行
  37. triggerEffects(this.dep)
  38. }
  39. }
  40. }
  41. // 重构
  42. // isObject的逻辑有重复的代码,使用 convert() 封装
  43. function convert(value) {
  44. return isObject(value) ? reactive(value) : value
  45. }
  46. // 依赖收集的逻辑
  47. function trackRefValue(ref) {
  48. // 在 ReactiveEffect 中的 track() 封装好依赖收集的方法
  49. // 把收集到的依赖,定义到这个类中 dep, 同样dep是一个Set
  50. // 又因为 track() 基于 target, 收集依赖,而 ref 只有一个 value ->对应一个dep
  51. // 将收集依赖的逻辑抽离出来 trackEffects(), 封装 dep 的传参
  52. // 执行到 trackEffects() 出错,因为 dep 可能是 undefined,和之前出现的原因一样
  53. // 因为 读取 .value 值时,有一些数据, 不会触发依赖收集, 没有使用到 effect, 就单纯的读取,所以 dep 可能是 undefined
  54. // 解决: 判断 dep 如果是 undefined, 直接返回读取的值
  55. // 在 effect -> isTracking() 函数有判断过 dep 的存在, 直接使用就行
  56. if (isTracking()) {
  57. // 如果 isTracking() 返回值 为 true, 说明 activeEffect 有值
  58. trackEffects(ref.dep)
  59. }
  60. }
  61. // ref
  62. export function ref(ref) {
  63. // 返回一个 new RefImpl 的实例
  64. return new RefImpl(ref)
  65. }
  66. /**
  67. * ref 的总体逻辑
  68. * 1. 因为 ref 传过来的值是一个单值, 1 -> "1"
  69. * 而且需要知道在什么是否调用 getter 和 setter,
  70. *
  71. * 代理对象使用的是 Proxy, 针对于 {} 对象,
  72. * 所以 ref 定义了 RefImpl 类,类中具有 get value 和 set value 的方法 , 这样就可以知道什么时候进行 get | set
  73. *
  74. * 2. 实现逻辑
  75. * - 基本的 ref , 通过 .value 访问值, 可以通过 RefImpl 中的 get value 和 set value 方法进行代理
  76. * - 实现响应式的ref, 需要用到 effect 和 effect 逻辑中的 track() 和 trigger() 方法 收集依赖 和 触发依赖
  77. * - 同样再 getter 和 setter 中,需要收集依赖和触发依赖
  78. * -
  79. *
  80. * - ref 收集获取依赖的逻辑 传入 dep, 它是一个Set(), 执行 trackEffect() 中 dep 会把 activeEffect 收集到 Set() 中,也就完成了 收集依赖的操作
  81. * - 当 执行到 setter 时 传入 this.dep 收集的依赖, 通过 triggerEffects()方法达到触发依赖的方法
  82. */
  1. // shared/index.ts
  2. export const hasChanged = (newValue, value) => {
  3. // 如果它们相等 -> false
  4. // 如果不等 -> true -> 才会执行触发依赖
  5. return !Object.is(newValue, value)
  6. }

3.12 实现 isRef & unRef

  • isRef 判断一个数据是不是 ref 数据
  • unRef 实现如果数据是ref , 获取 ref.value 的值

测试用例:
ref.spec.ts

  1. // 4. isRef() 判断是否是 ref
  2. it('isRef', () => {
  3. const a = ref(1)
  4. const user = reactive({ age: 1 })
  5. expect(isRef(a)).toBe(true)
  6. expect(isRef(1)).toBe(false)
  7. expect(isRef(user)).toBe(false)
  8. })
  9. // 5. unRef () 获取 xxx.value 的值
  10. it('unRef', () => {
  11. const a = ref(1)
  12. expect(unRef(a)).toBe(1)
  13. expect(unRef(1)).toBe(1)
  14. })

ref.ts 代码实现

  1. class RefImpl {
  2. // 设置一个内置变量,如果具有这个值,说明是ref
  3. public _v_isRef = true
  4. }
  5. // isRef
  6. export function isRef(ref) {
  7. // 实现逻辑: 在 RefImpl 定义一个 _v_isRef 的属性, 判断是否具有这个属性,如果有,说明是一个 ref
  8. // 使用 !! 转换 undefined
  9. return !!ref._v_isRef
  10. }
  11. // unRef
  12. export function unRef(ref) {
  13. // 实现逻辑:1. 判断 ref 是不是 Ref, 如果是,则返回 ref.value 的值,如果不是,则返回 ref
  14. return isRef(ref) ? ref.value : ref
  15. }

运行测试 …

3.13 实现 ref —— proxyRefs

proxyRefs 的功能,把 ref 的 .value 去掉,并且能够访问 ref .value的值

ref.spec.ts

  1. // 6. 实现 proxyRefs
  2. // proxyRefs 的功能,把 ref 的 .value 去掉,并且能够访问 ref .value的值
  3. it('proxyRefs', () => {
  4. const user = {
  5. age: ref(10),
  6. name: 'ZhangSan'
  7. }
  8. // 实现: get 方法 , 如果说 age(ref) 返回 .value
  9. // not ref 返回本身的值 value
  10. const proxyUser = proxyRefs(user)
  11. // 1. 通过proxyRefs 代理过的对象, 中有ref(),通过属性可以访问 值
  12. expect(proxyUser.age).toBe(10)
  13. expect(user.age.value).toBe(10)
  14. expect(proxyUser.name).toBe('ZhangSan')
  15. // 实现 set , 判读是否是 ref 类型 ,
  16. // true -> 修改 .value
  17. // false -> 那就是 ref(xxx) , 那就进行替换
  18. // 修改 user.age 的值,和 proxyUser.age 的值
  19. proxyUser.age = 20
  20. expect(user.age.value).toBe(20)
  21. expect(proxyUser.age).toBe(20)
  22. // 如果要修改的值是 ref 对象, 直接替换
  23. proxyUser.age = ref(10)
  24. expect(user.age.value).toBe(10)
  25. expect(proxyUser.age).toBe(10)
  26. // 实现场景:
  27. // 1. template -> 模板中的 ref 属性
  28. // vue3 -> setup(){return { ref(xxx) } }, 在模板中就不需要使用 .value
  29. })

ref.ts 的实现

  1. export function proxyRefs(objectWithRefs) {
  2. // 如何能知道 读取的值呢 ? 可以通过 new Proxy
  3. // get | set
  4. return new Proxy(objectWithRefs, {
  5. get(target, key) {
  6. // 实现: get 方法 , 如果说 age(ref) 返回 .value
  7. // not ref 返回本身的值 value
  8. // 使用 unRef() 判断就行
  9. return unRef(Reflect.get(target, key))
  10. },
  11. set(target, key, value) {
  12. // 实现 set , 判读 对象之前的 key, 是否是 ref 类型 ,
  13. // true -> 修改 .value
  14. // false -> 那就是 ref(xxx) , 那就进行替换
  15. // 如果之前对象的值,是一个 Ref 类型,并且修改的值 不是一个Ref 类型时
  16. if (isRef(target[key]) && !isRef(value)) {
  17. // 进行替换
  18. return target[key].value = value
  19. } else {
  20. return Reflect.set(target, key, value)
  21. }
  22. }
  23. })
  24. }

3.14 实现 computed

  • 计算属性类似 ref , 通过 返回值的 .value 访问属性值
  • 计算属性 接收一个参数, 参数->是一个函数
  • 参数属性 fn 具有一个返回值
  • 计算属性具有 缓存的效果 (重要)

实现最基础的 computed
computed.spec.ts

  1. describe('computed', () => {
  2. // 1. 实现 computed 属性
  3. it('happy path', () => {
  4. // - 计算属性类似 ref , 通过 返回值的 .value 访问属性值
  5. // - 计算属性 接收一个参数, 参数->是一个函数
  6. // - 参数属性 fn 具有一个返回值
  7. // - 计算属性具有 缓存的效果 (重要)
  8. const user = reactive({
  9. age: 1
  10. })
  11. const age = computed(() => {
  12. // 接收一个 fn , fn 具有一个返回值
  13. return user.age
  14. })
  15. // 测试1 : 计算属性返回值 通过 .value 访问
  16. expect(age.value).toBe(1)
  17. })
  18. })

computed.ts 中逻辑代码

  1. // 声明一个容器处理 computed
  2. class ComputedRefImpl {
  3. private _getter: any
  4. constructor(getter) {
  5. this._getter = getter
  6. }
  7. // 当访问 .value 时,返回 fn的的返回值
  8. get value() {
  9. return this._getter()
  10. }
  11. }
  12. // 使用 getter 接收 fn
  13. export function computed(getter) {
  14. return new ComputedRefImpl(getter)
  15. }

完善 computed

  • 实现缓存 功能
  • 当响应式数据发生变化时候,更新逻辑

    1. // 2. 实现缓存 功能
    2. // - 当响应式数据 .value 没有发生变化时候 | 二次调用时候,拿到的是缓存的值,也就是上一次的值
    3. it('should compute lazily', () => {
    4. const value = reactive({
    5. foo: 1
    6. })
    7. // 定义一个 getter 函数
    8. const getter = jest.fn(() => {
    9. // 返回一个 响应式的值
    10. return value.foo
    11. })
    12. // 初始化
    13. const cValue = computed(getter)
    14. // 测试1: 当没有使用 cValue,这个计算属性的值时, getter 不会被调用
    15. // lazy 懒执行
    16. expect(getter).not.toHaveBeenCalled()
    17. // 测试2: 当使用 cValue.value, 这个计算属性的值时,调用 getter()获取相应的值, 能够读取到相应的值
    18. expect(cValue.value).toBe(1)
    19. expect(getter).toHaveBeenCalledTimes(1) // getter 调用一次
    20. // 测试3: 当再次 读取cValue.value, 而是直接拿到缓存的值, 不是从 getter 获取的值
    21. cValue.value // 这里直接返回缓存的值, 不会执行 getter
    22. expect(getter).toHaveBeenCalledTimes(1)
    23. // 测试4: 当修改 响应式对象 value.foo 的值, getter 还是会执行一次
    24. // update
    25. value.foo = 2 // 收集trigger -> 使用 effect 收集 -> get 重新执行
    26. // 修改值,不会调用 getter, 而是执行 scheduler, 把 this._dirty 设置为true,
    27. // toHaveBeenCalledTimes(1) -> 是getter上一次的调用
    28. expect(getter).toHaveBeenCalledTimes(1)
    29. // 执行 trigger 完成修改值
    30. expect(cValue.value).toBe(2)
    31. // 当再次访问 cValue.value时,此时this._dirty 为 true, 这时候会调用 getter
    32. cValue.value
    33. expect(getter).toHaveBeenCalledTimes(2)
    34. })

    computed.ts逻辑代码
    effect 中导出 ReactiveEffect 类使用 ```typescript

class ComputedRefImpl { private _getter: any private _dirty: boolean = true // 控制缓存的变量 private _value: any private _effect: any constructor(getter) { this._getter = getter

  1. // 初始化 effect , 把 getter -> fn , 第二个参数 schelder, 当执行 tirgger() 会执行 schelder
  2. this._effect = new ReactiveEffect(getter, () => {
  3. // 第二次调用时候 不会一直执行 getter , 而是执行这里
  4. // 把 dirty 设置为 true
  5. if (!this._dirty) {
  6. this._dirty = true // 改为 true 后,修改时调用 this._effect.run()
  7. }
  8. })

} get value() { // 实现测试3,缓存功能 // 逻辑实现: 使用一个变量 dire 初始设置为true 判断, // 当初始时使用 .value 计算属性, 判断dire,true -> 表示初始 -> 执行返回值操作 ; dire -> false, 表示二次访问.value , 直接走缓存

  1. // 实现测试4,修改计算属性功能 响应式数据发生变化,
  2. // 重新执行 getter, 但是测试里,不希望执行原来的 getter, 还希望 dirty 为 true
  3. // 解决: 可以使用 effect 的第二个参数 -> scheduler 中实现
  4. // 使用 -> effect
  5. // 判断是否使用 缓存
  6. if (this._dirty) {
  7. // _dire -> true -> 初始操作
  8. this._dirty = false // 关闭 -> 锁上,不能使用初始化变量
  9. // this._value = this._getter()
  10. // 改为 调用 fn , 当 dirty 为 true 时,执行 fn
  11. this._value = this._effect.run()
  12. }
  13. // 返回 .value 的结果
  14. return this._value

} }

  1. `computed`总结

/**

  • 计算属性总结:
  • 计算属性:
    • 计算属性内部有一个 effect 和 getter value
    • 当用户调用 get value 时,会调用传过来的 fn, 然后把 fn 的值返回出去
    • 怎么做到缓存的
  • 使用了 this._dirty 这个变量,这个变量的作用是判断 这个 get value 有没有被调用过
    • 第一次调用时 -> 设置 this._dirty为 false , 返回 fn 的值
    • 第二次调用 -> 返回缓存的值
    • 当内部依赖响应式发生改变时, value.foo = 2
    • 会触发 trigger(), 然后会触发 effect 的第二个参数,-> schelder()这个函数, 因为在 trigger 中有判断 scheduler 是否存在
    • 执行 scheduler() -> 会把 dirty 设置为 true , 这样当用户再次调用 get value 重新会调用 _effect.run() -> fn , 得到最新的值,然后把他return 出去
  • */ ```

运行 pnpm test, 通过测试,说明 computed() 功能实现