TS手写简陋版reactive响应式原理(依赖收集,依赖更新)

最近博主看源码了解到了vue3的响应式原理

vue3的响应式实现是依赖收集与依赖更新,vue3已经从Object.property更换成ProxyProxy相比于前者可以直接监听对象数组,对于深层次的对象和数组,会把触发对应getter,然后去递归进行依赖收集,并不是直接像vue2暴力那样递归,总体而言性能更好

实现原理:

  • 通过Proxy(代理):拦截对象中任意属性的变化,包括属性的读写,属性的添加,属性的删除等。
  • 通过Reflect(反射):对源对象的属性进行操作

接下来博主带你们手写一波简单的源码操作

  • 对reactive传进来的对象进行Proxy进行劫持在内部进行依赖收集与通知更新操作
import { track,trigger } from "./effect"

//判断是否是对象的
const isObject = (target) => target!=null && typeof target == 'object'

export const reactive = <T extends object>(target:T) =>{
    return new Proxy(target,{
        //返回三个参数
        /**
         *
         * @param target 传入的当前对象
         * @param key 传入的当前属性
         * @param receiver 其实也是当前对象
         */
        get(target,key,receiver){

            // return target[key] //某些情况特定情况会上下文错乱
            //解决上下文错乱
            //Reflect ES新增 对象取值,接收三个参数
            let res = Reflect.get(target,key,receiver) as object //断言成object
            //依赖收集
            track(target,key)
            //深层次的递归
            if(isObject(res)){
                return reactive(res)
            }
            return res
        },
        //需要返回一个布尔值
        set(target,key,value,receiver){
            //Reflect.set正好返回一个布尔值
            let res = Reflect.set(target,key,value,receiver)
            //依赖更新
            trigger(target,key)
            return res
        }
    })
}

采用Reflet对对象进行标准化操作,因为如果直接采用JS如果失败了,不会产生异常提示

这样在进行获取数据是后进行依赖收集,在更新数据后进行通知依赖更新

补充知识点

1.Reflect.get()

Reflect.get(target, propertyKey[, receiver])
用于从对象中获取属性的值

target: 目标对象
propertyKey: 需要获取值的属性名称
receiver: 如果遇到getter,此值将提供给目标调用

2.Reflect.set()

Reflect.set(target, propertyKey, value[, receiver])

在对象上设置属性,返回一个Bool值来表示是否成功设置属性。

target: 目标对象
propertyKey: 设置的属性名称
value: 设置的属性值
receiver: 如果遇到setter, this将提供给目标调用

依赖收集

//依赖收集
/**
 * 
 * @param target 接收这个对象
 * @param key 
 */

//需要一个全局变量把它收集起来
//WeakMap只接收object的类型
//target正好是一个对象
const targetMap = new WeakMap()
export const track = (target,key) =>{
    let firstDeepMap = targetMap.get(target)
     //第一层数据结构
    //第一次是没有这个值的
    if(!firstDeepMap){
        //所以给它填充一下
        firstDeepMap = new Map()
        //通过targetMap 给它添加进去
        targetMap.set(target,firstDeepMap)
    }
     //第二层数据结构
     let secondDeepMap = firstDeepMap.get(key) //通过这个key 去取第二层的new Set
     //第一次没有值 取不到 需要填充
     if(!secondDeepMap){
        secondDeepMap = new Set()
        firstDeepMap.set(key,secondDeepMap)
     }
     //第三层把它关联起来(effect,effect...)
     secondDeepMap.add(activeEffect)
}

我们首先new了一个weakMap,weakMap只接受object的类型,target正好是一个对象,然后我们通过target获取到对应的内部Map,我们在通过key获取到Set的集合,此时内部存储的就是一个个所收集到的依赖

这里使用WeakMap原因是它是一个弱引用,不会影响垃圾回收机制回收。

activeEffect是什么呢

effect可以接收一个匿名的函数 客户可以自己去自定义

匿名函数我们每次把它收集起来 然后依赖发生变化时  去执行这个里面副作用函数,实现依赖收集和依赖更新

我们自己定义了一个简陋的版本,闭包收集去执行它

//effect可以接收一个匿名的函数 客户可以自己去自定义
//匿名函数我们每次把它收集起来 然后依赖发生变化时  去执行这个里面副作用函数,实现依赖收集和依赖更新

//定义一个全局变量 把这个闭包给收集起来
//简陋版本
let activeEffect;
export const effect = (fn:Function) =>{
    //闭包
    const _effect = function(){
        activeEffect = _effect //收集起来执行一下
        fn()
    }
    //首次默认给它调用一下
    _effect()
}

 我们简单的可以就把他理解成一个依赖,用户使用了effect函数过后,里面的响应式数据发生变化后会重新执行传递进去的回调函数,vue2中收集的依赖对应watcher,vue3收集的依赖实际是effect,他们两者实现功能实际上是一样的。

依赖更新

这里暂不考虑DOM问题,操作起来其实很简单就是通过被Proxy劫持的targetkey找到对应的Set集合调用用户传递的effect函数进行依赖更新

//依赖更新
//通过全局变量targetMap取到firstDeepMap
export const trigger = (target,key) =>{
    const firstDeepMap = targetMap.get(target)
    const secondDeepMap = firstDeepMap.get(key)
    //收集到的就是一个副作用函数effect,进行更新
    secondDeepMap.forEach(effect => effect())
}

则全部代码整理如下effect.ts


//effect可以接收一个匿名的函数 客户可以自己去自定义
//匿名函数我们每次把它收集起来 然后依赖发生变化时  去执行这个里面副作用函数,实现依赖收集和依赖更新

//定义一个全局变量 把这个闭包给收集起来
//简陋版本
let activeEffect;
export const effect = (fn:Function) =>{
    //闭包
    const _effect = function(){
        activeEffect = _effect //收集起来执行一下
        fn()
    }
    //首次默认给它调用一下
    _effect()
}

//依赖收集
/**
 * 
 * @param target 接收这个对象
 * @param key 
 */

//需要一个全局变量把它收集起来
//WeakMap只接收object的类型
//target正好是一个对象
const targetMap = new WeakMap()
export const track = (target,key) =>{
    let firstDeepMap = targetMap.get(target)
     //第一层数据结构
    //第一次是没有这个值的
    if(!firstDeepMap){
        //所以给它填充一下
        firstDeepMap = new Map()
        //通过targetMap 给它添加进去
        targetMap.set(target,firstDeepMap)
    }
     //第二层数据结构
     let secondDeepMap = firstDeepMap.get(key) //通过这个key 去取第二层的new Set
     //第一次没有值 取不到 需要填充
     if(!secondDeepMap){
        secondDeepMap = new Set()
        firstDeepMap.set(key,secondDeepMap)
     }
     //第三层把它关联起来(effect,effect...)
     secondDeepMap.add(activeEffect)
}

//依赖更新
//通过全局变量targetMap取到firstDeepMap
export const trigger = (target,key) =>{
    const firstDeepMap = targetMap.get(target)
    const secondDeepMap = firstDeepMap.get(key)
    //收集到的就是一个副作用函数effect,进行更新
    secondDeepMap.forEach(effect => effect())
}

最后index.html可以执行

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <div id="app"></div>

    <script type="module">
      import { reactive } from "./reactive.js";
      import { effect } from "./effect.js";
      const user = reactive({
        name: "xiaochen",
        age: 18,
        foo: {
          bar: {
            sss: 123,
          },
        },
      });
      effect(() => {
        document.querySelector(
          "#app"
        ).innerText = `${user.name} - ${user.age}-${user.foo.bar.sss}`;
      });

      setTimeout(() => {
        user.name = "dachen";
        setTimeout(() => {
          user.age = "23";
          setTimeout(() => {
            user.foo.bar.sss = 66666666;
          }, 1000);
        }, 1000);
      }, 2000);
    </script>
  </body>
</html>

猜你喜欢

转载自blog.csdn.net/weixin_42125732/article/details/130967594