最近、あるブロガーがソース コードを見て、vue3 の応答性の原則について学びました。
vue3 の応答性の高い実装は、依存関係の収集と依存関係の更新です。前者と比較して、vue3 はオブジェクト配列を直接監視Object.property
できます。深いレベルのオブジェクトと配列の場合、トリガーは対応しProxy
、依存関係を再帰的に収集します。これは、直接再帰的なものではありません。ブルートフォースは全体的にパフォーマンスが優れていますProxy
getter
vue2
実装原則:
- プロキシ経由: 属性の読み取りと書き込み、属性の追加、属性の削除など、オブジェクト内の属性に対する変更をインターセプトします。
- Reflect 経由: ソース オブジェクトのプロパティを操作します。
次に、ブロガーが手書きによる簡単なソース コード操作を順番に説明します。
- reactive によって渡されたオブジェクトのプロキシをハイジャックし、依存関係の収集と通知の更新操作を内部で実行します。
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: 値を取得する必要があるプロパティの名前 register
: getter が見つかった場合、この値はターゲット呼び出しに提供されます
2.Reflect.set()
Reflect.set(ターゲット, プロパティキー, 値[, レシーバー])
オブジェクトにプロパティを設定し、プロパティが正常に設定されたかどうかを示す Bool 値を返します。
target: ターゲット オブジェクト
propertyKey: プロパティ名の設定
value: プロパティ値の設定
レシーバ: setter が見つかった場合、これはターゲットの呼び出しに提供されます
依存関係のコレクション
//依赖收集
/**
*
* @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)
}
最初に新しいweakMapを作成します.weakMapはオブジェクトのタイプのみを受け入れます.ターゲットは正確にオブジェクトです.次に,ターゲットを通じて対応する内部Mapを取得します.キーを通じてSetコレクションを取得します.このとき、内部ストレージ収集された依存関係
ここで WeakMap が使用される理由は、これが弱い参照であり、ガベージ コレクション メカニズムに影響を与えないためです。
アクティブエフェクトとは何ですか?
エフェクトは匿名関数を受け取ることができ、顧客はそれを自分でカスタマイズできます
無名関数を収集して依存関係が変更されるたびに、内部で副作用関数を実行して依存関係の収集と依存関係の更新を実現します。
私たちは粗いバージョンを自分たちで定義し、それを実行するためにクロージャ コレクションを使用しました。
//effect可以接收一个匿名的函数 客户可以自己去自定义
//匿名函数我们每次把它收集起来 然后依赖发生变化时 去执行这个里面副作用函数,实现依赖收集和依赖更新
//定义一个全局变量 把这个闭包给收集起来
//简陋版本
let activeEffect;
export const effect = (fn:Function) =>{
//闭包
const _effect = function(){
activeEffect = _effect //收集起来执行一下
fn()
}
//首次默认给它调用一下
_effect()
}
単純に依存関係として理解すると、ユーザーがエフェクト関数を使用した後、内部の応答データが変更された後、渡されたコールバック関数が再実行されます。vue2 で収集された依存関係はウォッチャーに相当し、vue3 で収集された依存関係は実際には、それらが実装する関数は実際には同じです。
依存関係の更新
DOM
この問題は今のところ無視して、実際の操作は非常に簡単で、Proxy
ハイジャックされてtarget
見つかった対応する Set コレクションを通じてkey
ユーザーから渡されたエフェクト関数を呼び出し、依存関係を更新するだけです。
//依赖更新
//通过全局变量targetMap取到firstDeepMap
export const trigger = (target,key) =>{
const firstDeepMap = targetMap.get(target)
const secondDeepMap = firstDeepMap.get(key)
//收集到的就是一个副作用函数effect,进行更新
secondDeepMap.forEach(effect => effect())
}
すべてのコードは次のように構成されます。
//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>