Vue2的不足
Vue2使用Object.defineProperty
实现响应式,它有一些不足:
reactive的实现
Vue3使用ES6的Proxy实现响应式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import { track, trigger } from "./effect"
const isObject = (target:any)=> target!=null && typeof target == "object"
export const reactive = <T extends object>(target: T): T=> { return new Proxy(target, { get(target, key, receiver) { let res = Reflect.get(target, key, receiver) track(target, key) if(isObject(res)) { return reactive(res as object) } return res }, set(target, key, value, receiver) { let res = Reflect.set(target, key, value, receiver) trigger(target, key) return res }, }) }
|
effect
activeEffect收集副作用函数,初始化时也会调用一下,主要用来进行视图的改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| let activeEffect: Function
export const effect = (fn: Function)=> {
const _effect = function() { activeEffect = _effect fn() }
_effect() }
|
track
如图所示,通过以WeakMap为主的数据结构收集依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const targetMap = new WeakMap()
export const track = <T extends object> (target: T, key: string | symbol)=> {
let depsMap = targetMap.get(target) if(!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) }
let deps = depsMap.get(key) if(!deps) { deps = new Set() depsMap.set(key, deps) }
deps.add(activeEffect) }
|
trigger
更新依赖(就是调用这个值的副作用函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
export const trigger = <T extends object> (target: T, key: string | symbol) => { const depsMap = targetMap.get(target) if(!depsMap) { throw "111" } const deps = depsMap.get(key) if(!deps) { throw "222" }
deps.forEach((effect: Function)=> effect()) }
|
测试代码
要先把TypeScript代码编译成JavaScript代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <!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 person = reactive({ name: "yajue", age: 24, foo: { bar: { baz: 1919810 } } })
effect(()=> { document.querySelector("#app").innerText = `${person.name} - ${person.age} - ${person.foo.bar.baz}` })
setTimeout(() => { person.name = "yjsp" setTimeout(() => { person.age = 114514 setTimeout(() => { person.foo.bar.baz = 364364 }, 1000) }, 1000) }, 2000) </script> </body> </html>
|