반응형 응답 원칙의 TS 손으로 쓴 간단한 버전(종속성 수집, 종속성 업데이트)

최근 한 블로거가 소스 코드를 보고 vue3의 응답성 원칙에 대해 배웠습니다.

vue3의 반응형 구현은 종속성 수집 및 종속성 업데이트입니다. Vue3은 무차별 대입과 같은 직접 Object.property재귀 에서 전반적으로 더 나은 성능을 제공합니다.ProxyProxygettervue2

구현 원칙:

  • 프록시를 통해: 속성 읽기 및 쓰기, 속성 추가, 속성 삭제 등을 포함하여 객체의 모든 속성에 대한 변경 사항을 가로챕니다.
  • Reflect를 통해: 원본 개체의 속성에 대해 작업합니다.

다음으로, 블로거는 손으로 직접 쓰는 간단한 소스 코드 작업 과정을 안내합니다.

  • 반응형으로 전달된 객체의 프록시를 하이재킹하고 내부적으로 종속성 수집 및 알림 업데이트 작업을 수행합니다.
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
        }
    })
}

 

RefletJS를 직접 사용하는 경우 실패하면 예외가 생성되지 않으므로 객체에 대해 표준화된 작업을 사용하십시오 .

이런 방식으로 데이터를 획득한 후 종속성 수집을 수행하고, 데이터가 업데이트된 후에 종속성 업데이트를 알립니다.

보충 지식 포인트

1.Reflect.get()

Reflect.get(target, propertyKey[, Receiver])는
객체에서 속성 값을 가져오는 데 사용됩니다.

target: 대상 객체
propertyKey: 값을 가져와야 하는 속성의 이름
receive: getter가 발견되면 이 값이 대상 호출에 제공됩니다.

2.Reflect.set()

Reflect.set(대상, propertyKey, 값[, 수신자])

개체의 속성을 설정하고 속성이 성공적으로 설정되었는지 여부를 나타내는 Bool 값을 반환합니다.

target: 대상 객체
propertyKey: 속성 이름 설정
value: 속성 값 설정
Receiver: setter가 발견되면 대상 호출에 제공됩니다.

종속성 수집

//依赖收集
/**
 * 
 * @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)
}

먼저 새로운 WeakMap을 생성합니다. WeakMap은 객체 유형만 허용합니다. 대상은 정확히 객체입니다. 그런 다음 대상을 통해 해당 내부 Map을 얻습니다. 키를 통해 Set 컬렉션을 얻습니다. 이때 내부 저장소는 하나씩입니다. 수집된 종속성

여기서 WeakMap을 사용하는 이유는 약한 참조이고 가비지 수집 메커니즘에 영향을 주지 않기 때문입니다.

액티브이펙트란 무엇인가요?

효과는 익명 기능을 받을 수 있으며 고객이 직접 사용자 정의할 수 있습니다.

익명 함수와 종속성 변경 사항을 수집할 때마다 내부의 부작용 함수를 실행하여 종속성 수집 및 종속성 업데이트를 실현합니다.

우리는 조잡한 버전을 직접 정의하고 이를 실행하기 위해 클로저 컬렉션을 사용했습니다.

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

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

 단순히 종속성으로 이해하면 되는데, 사용자가 효과 함수를 사용한 후 전달된 콜백 함수는 내부 응답 데이터가 변경된 후 다시 실행됩니다. vue2에서 수집된 종속성은 감시자에 해당하고, vue3에서 수집된 종속성은 다음과 같습니다. 실제로 효과는 구현하는 기능이 실제로 동일합니다.

종속성 업데이트

지금은 문제를 무시하고 DOM작업은 실제로 매우 간단합니다. Proxy하이재킹되어 target찾은 해당 Set 컬렉션을 통해 key사용자가 전달한 효과 함수를 호출하여 종속성을 업데이트하는 것입니다.

//依赖更新
//通过全局变量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