2.实现reactive和effect

发布时间:2022-06-08 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了2.实现reactive和effect脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

reactive在vue中对象经过包裹后返回一个新的响应对象,主要原理是在get时进行依赖收集,在set是进行触发依赖,在2使用Object.definePRoPErty进行实现,在3使用Proxy对象进行改写实现方法,接下来我们使用proxy来实现reactive。

effect在vue中是一个响应函数,当函数内部的响应式对象发生改变时,effect会重新触发,主要实现原理是在effect第一次触发的时候,函数的响应式对象会触发自身的get方法,从而把effect收集起来,当响应式对象改变时,会把自身收集的effect函数重新触发。

首先在tsconfig.json中添加lib字段来支持DOM和es6

"lib": ["DOM","ES6"] 

创建以下文件

src
└── reactivITy
    ├── tests
    │   └── effect.spec.ts
    │   └── reactive.spec.ts
    └── effect.ts
    └── reactive.ts

实现reactive

在reactive下编写reactive方法并导出,方法的作用是:传进入一个对象,返回一个使用Proxy包裹的响应式对象。

后续在get的时候进行依赖收集,在set时候进行触发依赖。

// reactive.tsexport function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)

      // TODO 收集依赖
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      
      // TODO  触发依赖
      return res
    }
  });
}

编写测试代码进行测试,执行yarn jest命令

// reactive.spec.ts
import { reactive } From '../reactive'

describe('reactive', () => {
  it('happy path', () => {
    const user = {
      name: 'zs'
    }

    const obj = reactive(user)
    
    expect(obj.name).toBe('zs')

    // 响应式包裹的对象不等于原始对象
    expect(obj).not.toBe(user)
  })
})

实现effect

在effect文件下创建effect方法并导出,创建ReactiveEffect类将fn方法进行保存,在effect方法中初始化类并执行run方法。

// effect.ts
class ReactiveEffect {
  private _fn: any

  constructor(fn) {
    // 保存fn函数
    this._fn = fn
  }

  run() {
    this._fn()
  }
}

export function effect(fn) {
  const _effect = new ReactiveEffect(fn)
 
  // 第一次就执行   
  _effect.run()
}

编写测试代码进行测试,执行yarn jest命令,得到成功结果

// effect.spec.ts
import { effect } from '../effect'
import { reactive } from '../reactive'

describe('effect', () => {
  it('happy path', () => {
    const user = reactive({ age: 10 })
    let nextAge;
    
    effect(() => { 
    // 第一次就执行该函数
      nextAge = user.age + 1
    })

    expect(nextAge).toBe(11)
  })
})

接下来完善代码,实现依赖收集和触发

创建track方法收集依赖,使用activeEffect全局变量存储当前的effect函数

// effect.ts
// 修改ReactiveEffect
class ReactiveEffect {
  private _fn: any

  constructor(fn) {
    this._fn = fn
    // TODO 新加的一行代码,用于存储当前执行的effect函数
    activeEffect = this
  }

  run() {
    this._fn()
  }
}

const effectMap = new Map()
// 全局变量,用于获得当前的fn函数
let activeEffect

export function track(target, key) {
  // 在effectMap中查找
  let depsMap = effectMap.get(target)
  // effectMap中不存在,创建一个Map并把target当key,Map当value
  if (!depsMap) {
    depsMap = new Map()
    effectMap.set(target, effect)
  }

  // 在depsMap中查找key
  let deps = depsMap.get(key)
  if (!deps) {
    deps = new Set()
    depsMap.set(key, deps)
  }
  
  // 将正在执行的effect函数添加到deps中
  deps.add(activeEffect)
}

创建trigger方法触发所有存储的依赖

// effect.ts
export function trigger(target, key) {
  //  从map中查找,取出所有依赖并执行
  const depsMap = effectMap.get(target)

  const deps = depsMap.get(key)

  for (let dep of deps) {
    dep.run()
  }
}

最后在reactive中添加方法

import { track, trigger } from "./effect"

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      ...
      track(target, key)
      ...
    },
    set(target, key, value) {
      ...
      trigger(target,key)
      ...
    }
  });
}

编写测试代码进行测试,执行yarn jest命令,得到成功结果

// effect.spec.ts
import { effect } from '../effect'
import { reactive } from '../reactive'

describe('effect', () => {
  it('happy path', () => {
    const user = reactive({ age: 10 })
    let nextAge;

    effect(() => { 
      nextAge = user.age + 1
    })

    expect(nextAge).toBe(11)

    user.age++

    expect(nextAge).toBe(12)
  })
})

 

脚本宝典总结

以上是脚本宝典为你收集整理的2.实现reactive和effect全部内容,希望文章能够帮你解决2.实现reactive和effect所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。