目标 实现一个
<body>
<div id="app"></div>
</body>
<script>
// 从 Vue 中结构出 reactie、effect 方法
const { reactive, effect } = Vue
// 声明响应式数据 obj
const obj = reactive({
name: '张三'
})
// 调用 effect 方法
effect(() => {
document.querySelector('#app').innerText = obj.name
})
// 定时修改数据,视图发生变化
setTimeout(() => {
obj.name = '李四'
}, 2000)
</script>
大致过程就是
step1 使用proxy 创建实例, 完成基础reactive
创建 packages/shared/src/index.ts
/**
* 判断是否为一个对象
*/
export const isObject = (val: unknown) =>
val !== null && typeof val === 'object'
创建 packages/reactivity/src/reactive.ts
import { isObject } from '@vue/shared'
/**
* 创建响应式数据
* */
export function reactive(obj) {
if (!isObject(obj)) return;
return new Proxy(obj, {
})
}
创建 packages/reactivity/src/index.ts
export { reactive } from './reactive';
创建 packages/vue/src/index.ts
export { reactive, } from '@vue/reactivity';
对于 reactive 函数, 主要是通过proxy 来实现对数据的读写相应
/**
* 创建响应式数据
* */
import { isObject } from '@vue/shared'
/**
* 创建响应式数据
* */
export function reactive(obj) {
if (!isObject(obj)) return;
return new Proxy(obj, {
get(target, key) {
// 当访问proxy代理对象的属性时, 会执行get函数
// 读
console.log("进行读入了")
return target[key]
},
set(target, key, value) {
// 当设置proxy代理对象的属性时, 会执行set函数
// 写
target[key] = value;
console.log("进行写入改变了")
return true
}
})
}
然后 运行 npm run build 创建 dist文件
创建 example/reactivity/reactive-01.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>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="app">
<p id="p1"></p>
<p id="p2"></p>
</div>
</body>
<script>
const { reactive } = Vue
const obj = reactive({
name: '张三'
})
setTimeout(() => {
obj.name = '李四'
}, 2000)
console.log(obj.name);
</script>
</html>
实现最基本的 读写状态改变
step2 effect 触发副作用
创建packages/reactivity/src/effect.ts
原理就是 创建一个 副作用数组, 在 proxy读写的时候 将数组遍历执行
// 副作用函数数组
export const bucket = [] as Array<Function>;
// 只要有fn 就写入到
export function effect<T = any>(fn: () => T) {
// 先初始化执行一遍
fn();
bucket.push(fn);
}
在 reactive函数中
import { bucket } from './effect';
/**
* 创建响应式数据
* */
export function reactive(obj) {
...
return new Proxy(obj, {
...
set(target, key, value) {
// 当设置proxy代理对象的属性时, 会执行set函数
// 写
target[key] = value;
bucket.forEach((fn) => fn())
return true
}
}
}
在 packages/vue/src/index.ts
export { reactive, effect } from '@vue/reactivity';
然后 运行 npm run build 创建 dist文件
创建 example/reactivity/reactive.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>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="app">
<p id="p1"></p>
<p id="p2"></p>
</div>
</body>
<script>
const { reactive, effect } = Vue
const obj = reactive({
name: '张三'
})
// 调用 effect 方法
effect(() => {
document.querySelector('#p1').innerText = obj.name
})
effect(() => {
document.querySelector('#p2').innerText = obj.name
})
setTimeout(() => {
obj.name = '李四'
}, 2000)
</script>
</html>
可以看到 可以 通过 effect 去触发 相应修改
step3 实现依赖收集
对effect 和 reactive 进行优化
前面 执行副作用的时候 我们没有区分 对 proxy 不同属性 执行不同的副作用函数, 现在换成 WeekMap类型 对 属性进行收集
effect 由上面数组 bucket类型 改为 实例ReactiveEffect Map类型
/**
* 单例的,当前的 effect
*/
export let activeEffect: ReactiveEffect | undefined
/**
* effect 函数
* @param fn 执行方法
* @returns 以 ReactiveEffect 实例为 this 的执行函数
*/
/**
* effect 函数
* @param fn 执行方法
* @returns 以 ReactiveEffect 实例为 this 的执行函数
*/
export function effect<T = any>(fn: () => T) {
// 生成 ReactiveEffect 实例
const _effect = new ReactiveEffect(fn)
// 执行 run 函数
_effect.run()
}
/**
* 响应性触发依赖时的执行类
*/
/**
* 响应性触发依赖时的执行类
*/
export class ReactiveEffect<T = any> {
constructor(public fn: () => T) {}
run() {
// 为 activeEffect 赋值
activeEffect = this
// 执行 fn 函数
return this.fn()
}
}
在 reactive中 触发 读写 使用 track 和 trigger
import { track, trigger } from './effect';
/**
* 创建响应式数据
* */
export function reactive(obj) {
if (!isObject(obj)) return;
return new Proxy(obj, {
get(target, key) {
// 当访问proxy代理对象的属性时, 会执行get函数
// 读
// 收集依赖
track(target, key)
return target[key]
},
set(target, key, value) {
// 当设置proxy代理对象的属性时, 会执行set函数
// 写
target[key] = value;
// 触发依赖
trigger(target, key, value)
return true
}
})
}
在 packages/reactivity/src/effect.ts 中
track
export type Dep = Set<ReactiveEffect>
/**
* 依据 effects 生成 dep 实例
*/
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
return dep
}
type KeyToDepMap = Map<any, Dep>
/**
* 单例的,当前的 effect
*/
export let activeEffect: ReactiveEffect | undefined
/**
* 收集所有依赖的 WeakMap 实例:
* 1. `key`:响应性对象
* 2. `value`:`Map` 对象
* 1. `key`:响应性对象的指定属性
* 2. `value`:指定对象的指定属性的 执行函数
*/
const targetMap = new WeakMap<any, KeyToDepMap>()
/**
* 用于收集依赖的方法
* @param target WeakMap 的 key
* @param key 代理对象的 key,当依赖被触发时,需要根据该 key 获取
*/
export function track(target: object, key: unknown) {
console.log('track: 收集依赖')
// 如果当前不存在执行函数,则直接 return
if (!activeEffect) return
// 尝试从 targetMap 中,根据 target 获取 map
let depsMap = targetMap.get(target)
// 如果获取到的 map 不存在,则生成新的 map 对象,并把该对象赋值给对应的 value
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 获取指定 key 的 dep
let dep = depsMap.get(key)
//为指定 map,指定key 设置回调函数
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
trackEffects(dep)
}
/**
* 利用 dep 依次跟踪指定 key 的所有 effect
* @param dep
*/
export function trackEffects(dep: Dep) {
dep.add(activeEffect!)
}
trigger
/**
* 触发依赖的方法
* @param target WeakMap 的 key
* @param key 代理对象的 key,当依赖被触发时,需要根据该 key 获取
* @param newValue 指定 key 的最新值
* @param oldValue 指定 key 的旧值
*/
export function trigger(target: object, key?: unknown, newValue?: unknown) {
console.log('trigger: 触发依赖')
// 依据 target 获取存储的 map 实例
const depsMap = targetMap.get(target)
// 如果 map 不存在,则直接 return
if (!depsMap) {
return
}
let dep: Dep | undefined = depsMap.get(key)
// dep 不存在则直接 return
if (!dep) {
return
}
// 触发 dep
triggerEffects(dep)
}
/**
* 依次触发 dep 中保存的依赖
*/
export function triggerEffects(dep: Dep) {
// 把 dep 构建为一个数组
const effects = Array.isArray(dep) ? dep : [...dep]
// 依次触发
for (const effect of effects) {
triggerEffect(effect)
}
}
/**
* 触发指定的依赖
*/
export function triggerEffect(effect: ReactiveEffect) {
effect.run()
}
step4 对 reactive Proxy 实例 也形成 Map相关
原因 想对两个对象 都实现 proxy响应式
const obj = reactive({
name: '张三'
})
const obj2 = reactive({
name: '王四'
})
改造reactive, 执行reactive即为 生成Map实例
import { mutableHandlers } from './baseHandlers';
import { isObject } from '@vue/shared'
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
/**
* 响应性 Map 缓存对象
* key:target
* val:proxy
* */
export const reactiveMap = new WeakMap<object, any>()
/**
* 为复杂数据类型,创建响应性对象
* @param target 被代理对象
* @returns 代理对象
*/
export function reactive(target: object) {
return createReactiveObject(target, mutableHandlers, reactiveMap)
}
/**
* 创建响应性对象
* @param target 被代理对象
* @param baseHandlers handler
*/
function createReactiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<object, any>
) {
// 如果该实例已经被代理,则直接读取即可
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
if (!isObject(target)) return
// 未被代理则生成 proxy 实例
const proxy = new Proxy(target, baseHandlers)
// 缓存代理对象
proxyMap.set(target, proxy)
return proxy
}
新增packages/reactivity/src/baseHandlers.ts
import { track, trigger } from './effect';
/**
* getter 回调方法
*/
const get = createGetter()
function createGetter() {
return function get(target: object, key: string | symbol, receiver: object) {
// 利用 Reflect 得到返回值
const res = Reflect.get(target, key, receiver)
// 收集依赖
track(target, key)
return res
}
}
/**
* setter 回调方法
*/
const set = createSetter()
/**
* 创建 setter 回调方法
*/
function createSetter() {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) {
// 利用 Reflect.set 设置新值
const result = Reflect.set(target, key, value, receiver)
// 触发依赖
trigger(target, key, value)
return result
}
}
/**
* 响应式的handler
* */
export const mutableHandlers: ProxyHandler<object> = {
get,
set
}
以上就实现了 一个 基本的Proxy
参考文章