vue3 Proxy 原理 具体实现 (理解版)

vue3 的响应式 是通过 proxy 收集 触发 更新 来实现的,于是乎就自己写了一个。

export const react = (obj) => {
  // 追踪 判断数据是否已经变化
  const handlers = {
    get (target, key, receiver) {
      track(target, key); // 追踪依赖
      return Reflect.get(target, key, receiver);
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const result = Reflect.set(target, key, value, receiver);
      if (value !== oldValue) {
        trigger(target, key); // 触发更新
      }
      return result;
    },
  };
  const dependenciesMap = new Map(); // 存储依赖关系的 Map
  function track (target, key) {
    let dependencies = dependenciesMap.get(target);
    if (!dependencies) {
      dependencies = new Set();
      dependenciesMap.set(target, dependencies);
    }
    dependencies.add(key);
  }
  // 触发时机 数据发生变化时
  function trigger (target, key) {
    const dependencies = dependenciesMap.get(target);
    if (dependencies && dependencies.has(key)) {
      //  触发视图更新 
    }
  }
  return new Proxy(obj, handlers);
};

可以看到  触发视图更新 这一点 ,因为 proxy 无 用该数据的dom 信息。

vue3 人家在编译的时候 就会收集拥有 响应式数据的 dom 人家可用 proxy 来刷新,既然vue 可以收集 我们也定义收集依赖 。 这里 我在需要响应式数据的dom 中添加 data-react 属性 。

<template>
<span data-react="num">{{ dataNum.num }}</span>
<button @click="reactNumber"> num++ </button>
</template>
<script setup> 
import { react } from './views/components/proxys'
const dataNum = react(
  {
    num: 1,
    paker: 9,
    mater: '撒的撒'
  },
  'react'
)
const reactNumber= ()=>{
    dataNum.num ++
}
</script>

 这里定义 react(obj,str) obj 为我们的响应式对象 。 str 是html 中 data-react 属性 str 就为 react ,相同  你想设置 data-_mater 属性为响应式 只需要  str 改成 _mater 即可。 

下面 完善 相应式  proxys.js


let appElement
let elementsWithDataMater
export const react = (obj, str) => {
  // 获取 拥有响应式数据的 dom 元素
  window.onload = () => {
    appElement = document.getElementById('app');
    elementsWithDataMater = appElement.querySelectorAll(`[data-${str}]`);
  }
  // 追踪 判断数据是否已经变化
  const handlers = {
    get (target, key, receiver) {
      track(target, key); // 追踪依赖
      return Reflect.get(target, key, receiver);
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver);
      const result = Reflect.set(target, key, value, receiver);
      if (value !== oldValue) {
        trigger(target, key); // 触发更新
      }
      return result;
    },
  };
  const dependenciesMap = new Map(); // 存储依赖关系的 Map
  function track (target, key) {
    let dependencies = dependenciesMap.get(target);
    if (!dependencies) {
      dependencies = new Set();
      dependenciesMap.set(target, dependencies);
    }
    dependencies.add(key);
  }
  // 触发时机 数据发生变化时
  function trigger (target, key) {
    const dependencies = dependenciesMap.get(target);
    if (dependencies && dependencies.has(key)) {
      // 定义要改变的 dom
      let thisDom
      if (appElement) {
        elementsWithDataMater.forEach(function (element) {
          // 对比到具体 dom
          if (key == element.dataset[str]) {
            thisDom = element
          }
        });
        if (thisDom) {
          // 替换 dom 中的数据
          thisDom.innerText = target[key]
        }
      } else {
        console.error(`没找到该元素,请在要修改的元素中定义[data-${str}]属性`);
      }
    }
  }
  return new Proxy(obj, handlers);
};

这里 :

  window.onload = () => {

    appElement = document.getElementById('app');

    elementsWithDataMater = appElement.querySelectorAll(`[data-${str}]`);

  }

获取所有的拥有响应式数据 的 dom (这是我们自己获取,vue3 在这一步是编译时收集)

在 set 中 将这些 dom 元素对比data-react【我这里用时 用的 react 字段】  属性 提取出具体的dom ,将该dom 的innerText 替换成 最新数据  。这样具体到某个dom 视图刷新,应该是不太浪费性能的,因为这是必须做的。 

总结:遵循 收集 对比 刷新dom 这就非常容易实现, 我们无法再编译的时候就收集依赖,所有只能自定义收集依赖,这也相当于 约定 > 规则  。

应用场景 : 非vue 和 react 场景。 vue 有响应式 react 有 useState 。 这属于前端基本功能,应该不能太依赖于框架。 可写自定义 hook  用来动态验证一些数据的场景。

代码不多 可以在你们的vue 文件中试一试 。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EternityNotBug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值