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 文件中试一试 。