最近博主看源码了解到了vue3的响应式原理
vue3的响应式实现是依赖收集与依赖更新,vue3已经从Object.property
更换成Proxy
,Proxy
相比于前者可以直接监听对象数组,对于深层次的对象和数组,会把触发对应getter
,然后去递归进行依赖收集,并不是直接像vue2
暴力那样递归,总体而言性能更好
实现原理:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括属性的读写,属性的添加,属性的删除等。
- 通过Reflect(反射):对源对象的属性进行操作
接下来博主带你们手写一波简单的源码操作
- 对reactive传进来的对象进行Proxy进行劫持在内部进行依赖收集与通知更新操作
import { track,trigger } from "./effect"
//判断是否是对象的
const isObject = (target) => target!=null && typeof target == 'object'
export const reactive = <T extends object>(target:T) =>{
return new Proxy(target,{
//返回三个参数
/**
*
* @param target 传入的当前对象
* @param key 传入的当前属性
* @param receiver 其实也是当前对象
*/
get(target,key,receiver){
// return target[key] //某些情况特定情况会上下文错乱
//解决上下文错乱
//Reflect ES新增 对象取值,接收三个参数
let res = Reflect.get(target,key,receiver) as object //断言成object
//依赖收集
track(target,key)
//深层次的递归
if(isObject(res)){
return reactive(res)
}
return res
},
//需要返回一个布尔值
set(target,key,value,receiver){
//Reflect.set正好返回一个布尔值
let res = Reflect.set(target,key,value,receiver)
//依赖更新
trigger(target,key)
return res
}
})
}
采用Reflet
对对象进行标准化操作,因为如果直接采用JS如果失败了,不会产生异常提示
这样在进行获取数据是后进行依赖收集,在更新数据后进行通知依赖更新
补充知识点
1.Reflect.get()
Reflect.get(target, propertyKey[, receiver])
用于从对象中获取属性的值
target: 目标对象
propertyKey: 需要获取值的属性名称
receiver: 如果遇到getter,此值将提供给目标调用
2.Reflect.set()
Reflect.set(target, propertyKey, value[, receiver])
在对象上设置属性,返回一个Bool值来表示是否成功设置属性。
target: 目标对象
propertyKey: 设置的属性名称
value: 设置的属性值
receiver: 如果遇到setter, this将提供给目标调用
依赖收集
//依赖收集
/**
*
* @param target 接收这个对象
* @param key
*/
//需要一个全局变量把它收集起来
//WeakMap只接收object的类型
//target正好是一个对象
const targetMap = new WeakMap()
export const track = (target,key) =>{
let firstDeepMap = targetMap.get(target)
//第一层数据结构
//第一次是没有这个值的
if(!firstDeepMap){
//所以给它填充一下
firstDeepMap = new Map()
//通过targetMap 给它添加进去
targetMap.set(target,firstDeepMap)
}
//第二层数据结构
let secondDeepMap = firstDeepMap.get(key) //通过这个key 去取第二层的new Set
//第一次没有值 取不到 需要填充
if(!secondDeepMap){
secondDeepMap = new Set()
firstDeepMap.set(key,secondDeepMap)
}
//第三层把它关联起来(effect,effect...)
secondDeepMap.add(activeEffect)
}
我们首先new了一个weakMap,weakMap只接受object的类型,target正好是一个对象,然后我们通过target获取到对应的内部Map,我们在通过key获取到Set的集合,此时内部存储的就是一个个所收集到的依赖
这里使用WeakMap原因是它是一个弱引用,不会影响垃圾回收机制回收。
activeEffect是什么呢
effect可以接收一个匿名的函数 客户可以自己去自定义
匿名函数我们每次把它收集起来 然后依赖发生变化时 去执行这个里面副作用函数,实现依赖收集和依赖更新
我们自己定义了一个简陋的版本,闭包收集去执行它
//effect可以接收一个匿名的函数 客户可以自己去自定义
//匿名函数我们每次把它收集起来 然后依赖发生变化时 去执行这个里面副作用函数,实现依赖收集和依赖更新
//定义一个全局变量 把这个闭包给收集起来
//简陋版本
let activeEffect;
export const effect = (fn:Function) =>{
//闭包
const _effect = function(){
activeEffect = _effect //收集起来执行一下
fn()
}
//首次默认给它调用一下
_effect()
}
我们简单的可以就把他理解成一个依赖,用户使用了effect函数过后,里面的响应式数据发生变化后会重新执行传递进去的回调函数,vue2中收集的依赖对应watcher,vue3收集的依赖实际是effect,他们两者实现功能实际上是一样的。
依赖更新
这里暂不考虑DOM
问题,操作起来其实很简单就是通过被Proxy
劫持的target
与key
找到对应的Set集合调用用户传递的effect函数进行依赖更新
//依赖更新
//通过全局变量targetMap取到firstDeepMap
export const trigger = (target,key) =>{
const firstDeepMap = targetMap.get(target)
const secondDeepMap = firstDeepMap.get(key)
//收集到的就是一个副作用函数effect,进行更新
secondDeepMap.forEach(effect => effect())
}
则全部代码整理如下effect.ts
//effect可以接收一个匿名的函数 客户可以自己去自定义
//匿名函数我们每次把它收集起来 然后依赖发生变化时 去执行这个里面副作用函数,实现依赖收集和依赖更新
//定义一个全局变量 把这个闭包给收集起来
//简陋版本
let activeEffect;
export const effect = (fn:Function) =>{
//闭包
const _effect = function(){
activeEffect = _effect //收集起来执行一下
fn()
}
//首次默认给它调用一下
_effect()
}
//依赖收集
/**
*
* @param target 接收这个对象
* @param key
*/
//需要一个全局变量把它收集起来
//WeakMap只接收object的类型
//target正好是一个对象
const targetMap = new WeakMap()
export const track = (target,key) =>{
let firstDeepMap = targetMap.get(target)
//第一层数据结构
//第一次是没有这个值的
if(!firstDeepMap){
//所以给它填充一下
firstDeepMap = new Map()
//通过targetMap 给它添加进去
targetMap.set(target,firstDeepMap)
}
//第二层数据结构
let secondDeepMap = firstDeepMap.get(key) //通过这个key 去取第二层的new Set
//第一次没有值 取不到 需要填充
if(!secondDeepMap){
secondDeepMap = new Set()
firstDeepMap.set(key,secondDeepMap)
}
//第三层把它关联起来(effect,effect...)
secondDeepMap.add(activeEffect)
}
//依赖更新
//通过全局变量targetMap取到firstDeepMap
export const trigger = (target,key) =>{
const firstDeepMap = targetMap.get(target)
const secondDeepMap = firstDeepMap.get(key)
//收集到的就是一个副作用函数effect,进行更新
secondDeepMap.forEach(effect => effect())
}
最后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>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { reactive } from "./reactive.js";
import { effect } from "./effect.js";
const user = reactive({
name: "xiaochen",
age: 18,
foo: {
bar: {
sss: 123,
},
},
});
effect(() => {
document.querySelector(
"#app"
).innerText = `${user.name} - ${user.age}-${user.foo.bar.sss}`;
});
setTimeout(() => {
user.name = "dachen";
setTimeout(() => {
user.age = "23";
setTimeout(() => {
user.foo.bar.sss = 66666666;
}, 1000);
}, 1000);
}, 2000);
</script>
</body>
</html>