安装响应式模块
shell
pnpm install @vue/reactivity -w
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
<script type="module">
import {
reactive,
effect
} from '/node_modules/@vue/reactivity/dist/reactivity.esm-browser.js';
const state = reactive({ name: 'erxiao', age: 30 });
effect(() => {
// 副作用函数 默认执行一次,响应式数据变化后再次执行
app.innerHTML = state.name + '今年' + state.age + '岁了';
});
setTimeout(() => {
state.age++;
}, 1000);
</script>
</html>
reactive
方法会将对象变成 proxy 对象, effect
中使用reactive
对象时会进行依赖收集,稍后属性变化时会重新执行effect
函数~。
1.编写 reactive 函数
typescript
import { isObject } from "@vue/shared";
/**
* 创建响应式对象
* @param target 目标对象
* @returns 代理对象
*/
export function reactive(target: object) {
return createReactiveObject(target)
}
/**
* 统一创建响应式对象
* @param target 目标对象
* @returns 代理对象
*/
export function createReactiveObject(target: object) {
}
由此可知这些方法接受的参数必须是一个对象类型。否则没有任何效果
typescript
const reactiveMap = new WeakMap(); // 缓存列表
const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
// 等会谁来取值就做依赖收集
const res = Reflect.get(target, key, receiver);
return res;
},
set(target, key, value, receiver) {
// 等会赋值的时候可以重新触发effect执行
const result = Reflect.set(target, key, value, receiver);
return result;
}
};
function createReactiveObject(target: object) {
if (!isObject(target)) {
return target;
}
const exisitingProxy = reactiveMap.get(target); // 如果已经代理过则直接返回代理后的对象
if (exisitingProxy) {
return exisitingProxy;
}
const proxy = new Proxy(target, mutableHandlers); // 对对象进行代理
reactiveMap.set(target, proxy);
return proxy;
}
这里必须要使用 Reflect 进行操作,保证 this 指向永远指向代理对象
typescript
import { isObject } from "@vue/shared";
/**
* 统一创建响应式对象
* @param target 目标对象
* @returns 代理对象
*/
export function createReactiveObject(target: object) {
// 统一判断是否是对象
if (!isObject(target)) return target
// 如果已经代理过,则直接返回代理对象
const existingProxy = reactiveMap.get(target)
if (existingProxy) return existingProxy
// 代理
const proxy = new Proxy(target, mutableHandlers)
// 缓存代理对象
reactiveMap.set(target, proxy)
return proxy
}
/**
* 代理对象的处理器
*/
const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
return res
}
}
将对象使用 proxy 进行代理,如果对象已经被代理过,再次重复代理则返回上次代理结果。 那么,如果将一个代理对象传入呢?
typescript
import { isObject } from "@vue/shared";
const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
/**
* 统一创建响应式对象
* @param target 目标对象
* @returns 代理对象
*/
export function createReactiveObject(target: object) {
if (target[ReactiveFlags.IS_REACTIVE]) {
// 在创建响应式对象时先进行取值,看是否已经是响应式对象
return target
}
// 统一判断是否是对象
if (!isObject(target)) return target
// 如果已经代理过,则直接返回代理对象
const existingProxy = reactiveMap.get(target)
if (existingProxy) return existingProxy
// 代理
const proxy = new Proxy(target, mutableHandlers)
// 缓存代理对象
reactiveMap.set(target, proxy)
return proxy
}
/**
* 代理对象的处理器
*/
const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
const res = Reflect.get(target, key, receiver)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
return res
}
}
这样我们防止重复代理就做好了~~~
这里我们为了代码方便维护,我们将mutableHandlers
抽离出去到baseHandlers.ts
中。
typescript
import { isObject } from "@vue/shared";
import { mutableHandlers, ReactiveFlags } from "./baseHandlers";
/**
* 创建响应式对象
* @param target 目标对象
* @returns 代理对象
*/
export function reactive(target: object) {
return createReactiveObject(target)
}
const reactiveMap = new WeakMap()
/**
* 统一创建响应式对象
* @param target 目标对象
* @returns 代理对象
*/
export function createReactiveObject(target: object) {
if (target[ReactiveFlags.IS_REACTIVE]) {
// 在创建响应式对象时先进行取值,看是否已经是响应式对象
return target
}
// 统一判断是否是对象
if (!isObject(target)) return target
// 如果已经代理过,则直接返回代理对象
const existingProxy = reactiveMap.get(target)
if (existingProxy) return existingProxy
// 代理
const proxy = new Proxy(target, mutableHandlers)
// 缓存代理对象
reactiveMap.set(target, proxy)
return proxy
}
typescript
const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
/**
* 代理对象的处理器
*/
const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
const res = Reflect.get(target, key, receiver)
return res
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver)
return res
}
}
export {
mutableHandlers,
ReactiveFlags
}
2.编写 effect 函数
typescript
/**
* 创建effect
* @param fn 回掉函数
* @param options 配置项
*/
export function effect(fn, options?) {
// 创建ReactiveEffect实例,传入回调函数和调度器
const _effect = new ReactiveEffect(fn, () => {
_effect.run()
})
// 立即执行一次副作用函数(首次依赖收集)
_effect.run()
}
export let activeEffect: ReactiveEffect | undefined = undefined
/**
* 依赖收集
*/
class ReactiveEffect {
public active = true
constructor(public fn, public scheduler) { }
run() {
console.log('run')
// 非激活状态直接执行函数
if (!this.active) return this.fn()
// 设置当前活跃的effect
let lastEffect = activeEffect
try {
// 设置当前活跃的effect
activeEffect = this
// 执行回调函数
return this.fn()
} finally {
// 执行完恢复上一个活跃的effect
activeEffect = lastEffect
}
}
}
3.依赖收集
默认执行effect
时会对属性,进行依赖收集
typescript
const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
// 取值的时候应该让响应式数据与effect函数建立联系
const res = Reflect.get(target, key, receiver)
track(target, key); // 依赖收集
return res
},
}
4.track 方法实现
reactiveEffect.ts
typescript
const targetMap = new WeakMap(); // 记录依赖关系
export const createDep = (cleanup, key) => {
const dep = new Map() as any;
dep.cleanup = cleanup; // 给清理依赖埋下伏笔
dep.name = key;
return dep;
};
export function track(target, key) {
// {name: 'erxiao', age: 30} name
//console.log('track', target, key)
// 如果存在活跃的effect,则进行依赖收集
if (activeEffect) {
// console.log('track', target, key)
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, dep = createDep(() => depsMap.delete(key), key))
}
trackEffect(activeEffect, dep)
}
}
将属性和对应的 effect 维护成映射关系,后续属性变化可以触发对应的 effect 函数重新run
effect.ts
typescript
class ReactiveEffect {
_depsLength = 0; // 用于记录依赖的个数
_trackId = 0; // 用于记录收集的次数
deps = [];
// ...
}
export function trackEffect(effect, dep) {
// 属性记住effect
dep.set(effect, effect._trackId);
// effect记住依赖
effect.deps[effect._depsLength++] = dep;
}
5.触发更新
typescript
set(target, key, value, receiver) {
// 设置值的时候应该让对应的effect重新执行
let oldValue = target[key]
const res = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
trigger(target, key, value, oldValue)
}
return res
}
reactiveEffect.ts
typescript
export function trigger(target, key, value, oldValue) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
triggerEffect(dep)
}
}
effect.ts
typescript
export function triggerEffects(dep) {
for (const effect of dep.keys()) {
if (effect.scheduler) {
effect.scheduler();
}
}
}
6.依赖清除
在渲染时我们要避免副作用函数产生的遗留
typescript
const state = reactive({ flag: true, name: 'erxiao', age: 30 });
effect(() => {
// 副作用函数 (effect执行渲染了页面)
console.log('render');
document.body.innerHTML = state.flag ? state.name : state.age;
});
setTimeout(() => {
state.flag = false;
setTimeout(() => {
console.log('修改name,原则上不更新');
state.name = 'zf';
}, 1000);
}, 1000);
export class ReactiveEffect {
// ...
run() {
// ...
try {
activeEffect = this;
preCleanEffect(this); // 每次渲染前重新进行依赖收集
return this.fn();
} finally {
activeEffect = lastEffect;
}
}
}
function preCleanEffect(effect) {
// 执行函数前先进行清理操作
effect._trackId++;
effect._depsLength = 0;
}
重新进行依赖收集时,需要做比对
typescript
export function trackEffect(effect, dep) {
// 如果一个属性多次收集,需要排除
if (dep.get(effect) !== effect._trackId) {
// 更新id
dep.set(effect, effect._trackId);
// 获取上次的依赖,比对是否有变化
const oldDep = effect.deps[effect._depsLength];
// {flag, name} 1
// {flag, age} 2
// 如果有变化,需要清理以前的,并且塞入新的
if (oldDep !== dep) {
if (oldDep) {
cleanDepEffect(oldDep, effect);
}
// 记录effect对应的依赖
effect.deps[effect._depsLength++] = dep;
} else {
effect._depsLength++;
}
}
}
删除掉收集器中对应的 effect
typescript
function cleanDepEffect(dep, effect) {
dep.delete(effect);
if (dep.size === 0) {
dep.cleanup(); // 清理此属性
}
}
销毁多于的依赖
typescript
try {
activeEffect = this;
preCleanEffect(this); // 每次渲染前重新进行依赖收集
return this.fn();
} finally {
postCleanEffect(this); // 清理依赖
activeEffect = lastEffect;
}
function postCleanEffect(effect) {
// 重新做收集后,看依赖列表有没有增加,有增加就要删除 (map是不能添加重复的)
if (effect.deps.length > effect._depsLength) {
// 仅处理多出来的删除即可
for (let i = effect._depsLength; i < effect.deps.length; i++) {
cleanDepEffect(effect.deps[i], effect);
}
effect.deps.length = effect._depsLength;
}
}
7.调度执行
trigger
触发时,我们可以自己决定副作用函数执行的时机、次数、及执行方式
typescript
export function effect(fn, options?) {
// 创建一个响应式effect 数据变化后可以重新执行
// 创建一个effect,只要依赖的属性变化了就要执行回调
const _effect = new ReactiveEffect(fn, () => {
_effect.run();
});
_effect.run();
if (options) {
// 合并参数
Object.assign(_effect, options);
}
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner; // 返回run方法
}
8.防止递归调用
如果本次在执行effect的时候更新了数据 不会触发effect的更新
typescript
effect(() => {
document.getElementById('app').innerHTML = state.name
state.name = Math.random()
})
通过 running 属性防止递归调用
typescript
class ReactiveEffect {
public active = true
// 记录当前effect执行了几次
_trackId = 0
deps = [];
_depsLength = 0; // 用于记录依赖的个数
_running = 0
constructor(public fn, public scheduler) { }
run() {
// 非激活状态直接执行函数
if (!this.active) return this.fn()
// 设置当前活跃的effect
let lastEffect = activeEffect
try {
// 设置当前活跃的effect
activeEffect = this
// 在收集依赖前清空上一个活跃的effect的依赖
preCleanEffect(this)
this._running++
// 执行回调函数 收集依赖
return this.fn()
} finally {
this._running--
// 删除多余的依赖
postCleanEffect(this)
// 执行完恢复上一个活跃的effect
activeEffect = lastEffect
}
}
}
typescript
export function triggerEffect(dep) {
for (const effect of dep.keys()) {
if (effect.scheduler) {
// 判断是否在执行 如果在执行就不执行
if (!effect._running)
effect.scheduler();
}
}
}
9.深度代理
typescript
effect(() => {
document.getElementById('app').innerHTML = state.user.city
})
setTimeout(() => {
state.user.city = 'beijing'
}, 1000);
typescript
get(target, key, receiver) {
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
// 取值的时候应该让响应式数据与effect函数建立联系
const res = Reflect.get(target, key, receiver)
track(target, key); // 依赖收集
// 判断取值是否是对象
if (isObject(res)) return reactive(res)
return res
},
当取值时返回的值是对象,则返回这个对象的代理对象,从而实现深度代理