reative原理详解:https://blog.csdn.net/weixin_39503495/article/details/127058245
使用typescript、vite构建工具
// 初始化项目
npm init -y
// 安装依赖
npm i vite typescript -D
初始化typescript。
tsc --init
创建index.html、index.ts,vite配置文件vite.config.ts、src目录。
在indext.html中引入index.ts
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>Documenttitle>
head>
<body>
<script src="./index.ts" type="module">script>
body>
html>
vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
server: {
port: 3000,
}
});
|-- index.html
|-- index.ts
|-- package-lock.json
|-- package.json
|-- src
| |-- dep.ts
| |-- effect.ts
| |-- handler.ts
| |-- reactive.ts
| `-- utils.ts
|-- tsconfig.json
`-- vite.config.ts
基本功能:将object类型数据处理为响应式数据。
基本实现:
递归调用reactive(res)进一步代理。将每个proxy实例放到全局map。const proxyMap = new WeakMap();
export const reactive = function <T extends object>(target: T): T {
const handler: ProxyHandler<any> = {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (isObject(res)) return reactive(res);
return res;
},
set(target, key, newVal, receiver) {
return Reflect.set(target, key, newVal, receiver);
},
};
const existingProxy = proxyMap.get(target);
if (existingProxy) return existingProxy;
const proxy = new Proxy<T>(target, handler);
proxyMap.set(target, proxy);
return proxy;
};
export const isObject = (target: any) =>
typeof target === "object" && target !== null;
handler.ts,isObject放到utils.ts中reactive.ts
import { handler } from './handler';
const proxyMap = new WeakMap();
export const reactive = function <T extends object>(target: T): T {
const existingProxy = proxyMap.get(target);
if (existingProxy) return existingProxy;
const proxy = new Proxy<T>(target, handler);
proxyMap.set(target, proxy);
return proxy;
};
将getter和setter抽离,在get时触发track追踪收集依赖,在set时触发trigger触发依赖更新。
import { reactive } from "./reactive";
import { isObject } from "./utils";
export const handler: ProxyHandler<any> = {
get: createGetter(),
set: createSetter(),
};
const createGetter = () => {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 追踪收集依赖
track(res)
if (isObject(res)) return reactive(res);
return res;
};
};
const createSetter = () => {
return function set(target, key, newVal, receiver) {
// 触发依赖的数据或视图更新
trigger(target)
return Reflect.set(target, key, newVal, receiver);
};
};
实现Class ReactiveEffect、effect,最后实现track和trigger
基本功能:_fn属性保存视图或数据更新函数,响应式数据被获取时触发getter,ReactiveEffect实例会被对应的depsMap收集,当响应式数据被修改时,会遍历depsMap,调用每一项(即ReactiveEffect实例)的run方法进而调用更新函数_fn,实现视图更新。
基本功能:追踪依赖。
实现方式:全局申明一个targetMap,利用target从targetMap中获取到依赖Map即depsMap,利用key值从depsMap中获取对应的deps。deps是一个集合Set,每一项都是一个ReactiveEffect实例(后边会实现)。
// 类型
type ReactiveEffect = {
run: () => any
}; // 暂时不管,后续会实现Class ReactiveEffect
type DepsMap = Set<ReactiveEffect>;
const targetMap = new Map<any, Map<string, DepsMap>>();
let reactiveEffect: ReactiveEffect | undefined;
export const track = (target: any, key: string) => {
if (reactiveEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let deps = depsMap.get(key);
if (!deps) depsMap.set(key, (deps = new Set<ReactiveEffect>()));
trackEffect(deps);
}
};
const trackEffect = (deps: DepsMap) => {
deps.add(reactiveEffect as ReactiveEffect);
};
注:targetMap结构
targetMap: Map {
target1: Map {
key1: Set,
key2: Set
},
target2: Map {
key1: Set
}
}
基本功能:让数据对应的依赖调用run方法,实现视图或数据更新。
实现方式:获取当前target当前key的deps,deps转化为数组effects: ReactiveEffect[],遍历effects调用ReactiveEffect.run方法。
export const trigger = (target: any, key: string) => {
let depsMap = targetMap.get(target) as Map<string, DepsMap>;
let deps = depsMap.get(key) as DepsMap;
triggerEffect([...deps])
};
const triggerEffect = (effects: ReactiveEffect[]) => {
for (let effect of effects) effect.run()
}
基本功能:每次视图或数据使用到某一个响应式数据时,将视图或数据更新函数fn作为
<div id="x">我是X,我使用到{{A}}<div>
<div id="y">我是Y,我使用到{{A}}<div>
...
const obj = reactive({
A: "qq"
})
...
effect函数,将当前X的更新函数fn传入。// 调用effect生成effect实例
effect(fn);
setTimeout(() => {
obj.A = "wechat";
}, 1000);
// x元素的视图更新函数
function fn() {
resetXElement();
const $x = document.querySelector("#x");
$x && ($x.innerHTML = $x.innerHTML.replace(/\{\{A\}\}/, obj.A));
}
// 由于模板{{A}}被替换成A的值,每次都要重置模板
function resetXElement() {
const $x = document.querySelector("#x");
$x && ($x.innerHTML = "我是X,我使用到{{A}}");
}
effect函数传入fn生成ReactiveEffect实例放到全局的变量reactiveEffect,通过调用实例的run方法进而调用fn。let reactiveEffect;
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
class ReactiveEffect {
...
constructor(fn) {
this._fn = fn
}
run() {
reactiveEffect = this
return this._fn()
}
}
fn访问到A,触发A的getter,进而触发track,由于全局的变量reactiveEffect有值,数据需要被track,找到A对应的depsMap,将实例放入其中。function fn() {
...
// 访问到了obj.A
$x && ($x.innerHTML = $x.innerHTML.replace(/\{\{A\}\}/, obj.A));
}
// 触发getter
function get(target, key) {
track()
}
// 触发track
function track(target, key) {
if (reactiveEffect) {
// 由target(即此处的obj)获取depsMap
const depsMap = targetMap.get(target)
// 由key(即A)获取deps
const deps = depsMap.get(key)
// 收集依赖
deps.add(reactiveEffect) // 此时的deps为Set[reactiveEffect1],reactiveEffect1中有x的更新函数
}
}
Y使用到A同理,创建一个ReactiveEffect实例,会被A对应的depsMap收集起来。
当A数据发生改变时,只要找到对应的depsMap,遍历调用每一项的run方法,进而调用fn,实现视图或数据的更新。
src目录里的文件
// reactive.ts
import { handler } from './handler';
const proxyMap = new WeakMap();
export const reactive = function <T extends object>(target: T): T {
const existingProxy = proxyMap.get(target);
if (existingProxy) return existingProxy;
const proxy = new Proxy<T>(target, handler);
proxyMap.set(target, proxy);
return proxy;
};
// handler.ts
import { reactive } from "./reactive";
import { isObject } from "./utils";
import { track, trigger } from "./effect";
const createGetter = () => {
return function get(target: Object, key: string, receiver: any) {
const res = Reflect.get(target, key, receiver);
track(target, key);
if (isObject(res)) return reactive(res);
return res;
};
};
const createSetter = () => {
return function set(target: Object, key: string, newVal: any, receiver: any) {
trigger(target, key);
return Reflect.set(target, key, newVal, receiver);
};
};
export const handler: ProxyHandler<any> = {
get: createGetter(),
set: createSetter(),
};
// effect.ts
import { Dep } from "./dep";
type DepsMap = Set<ReactiveEffect>;
const targetMap = new Map<any, Map<string, DepsMap>>();
let reactiveEffect: ReactiveEffect | undefined;
export const track = (target: any, key: string) => {
if (reactiveEffect) {
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let deps = depsMap.get(key);
if (!deps) depsMap.set(key, (deps = new Set<ReactiveEffect>()));
trackEffect(deps);
}
};
const trackEffect = (deps: DepsMap) => {
deps.add(reactiveEffect as ReactiveEffect);
};
export const trigger = (target: any, key: string) => {
let depsMap = targetMap.get(target) as Map<string, DepsMap>;
let deps = depsMap.get(key) as DepsMap;
triggerEffect([...deps]);
};
const triggerEffect = (effects: ReactiveEffect[]) => {
for (let effect of effects) effect.run();
};
export const effect = function <T>(fn: (...arg: any) => T) {};
export class ReactiveEffect<T = any> {
private _fn: any;
constructor(fn: () => T) {
this._fn = fn;
}
run() {
reactiveEffect = this;
this._fn();
}
}
// dep.ts
import { ReactiveEffect } from "./effect"
export type Dep = Set<ReactiveEffect> & {w:number, n:number}
export const createDep = (effects?: any): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}
// utils
export const isObject = (target: any) =>
typeof target === "object" && target !== null;
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>Documenttitle>
head>
<body>
<script src="./index.ts" type="module">script>
body>
html>
index.ts
import { effect } from "./packages/reactivity/src/effect";
import { reactive } from "./packages/reactivity/src/reactive";
const obj = reactive({
A: "qq",
});
window.addEventListener("load", handler);
function handler() {
let fnX = updateXElement
effect(fnX);
setTimeout(() => {
obj.A = "wechat";
}, 1000);
}
function updateXElement() {
let $x = document.querySelector("#x");
resetXElement($x as Element);
$x && ($x.innerHTML = $x.innerHTML.replace(/\{\{A\}\}/, obj.A));
}
function resetXElement($x: Element) {
$x && ($x.innerHTML = "我是X,我使用到{{A}}");
}
vite.config.ts
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
server: {
port: 3000,
},
});
package.json
...
"scripts": {
"dev": "npx vite --host"
},
...