vue3 响应式原理的实现,源码地址:vue3_reactivity
rollup 搭建ts环境
装置 rollup 插件
npm install rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-serve typescript -D
包名 | 性能 |
---|---|
rollup | 打包工具 |
rollup-plugin-typescript2 | 解析ts插件 |
@rollup/plugin-node-resolve | 解析第三方模块 |
@rollup/plugin-replace | 替换插件 |
rollup-plugin-serve | 启动本地服务插件 |
配置打包环境
生成 tsconfig.json
文件:
npx tsx --init
批改 tsconfig.json
配置属性 module
为 ESNext
(默认在浏览器运行)
能够设置strict
属性为false,让 ts 反对 any 类型,scouceMap
须要设置成 true,不便调试代码
根目录新建 rollup.config.js
配置文件,并输出上面内容:
import path from "path";import ts from "rollup-plugin-typescript2";import { nodeResolve } from "@rollup/plugin-node-resolve";import replace from "@rollup/plugin-replace";import serve from "rollup-plugin-serve";export default { input: 'src/index.ts', output: { name: 'VueReactivity', format: 'umd', file: path.resolve('dist/VueReactivity.js'), sourcemap: true, }, plugins: [ nodeResolve({ extensions: ['.js', '.ts'], }), ts({ tsconfig: path.resolve(__dirname, 'tsconfig.json'), }), replace({ "process.env.NODE_ENV": JSON.stringify("development"), }), serve({ open: true, openPage: "/public/index.html", port: 3000, contentBase: "" }) ],}
新建入口文件srx/index.ts
和模板文件public/index.html
。
模板文件 index.html 须要手动引入打包后的 /dist/vue.js
package.json
增加打包命令:
"scripts": { "dev": "rollup -c -w"}
reactivity模块
先看看Vue的reactivity
模块实现成果,先装置 reactivity
:
npm install @vue/reactivity
测试 public/index.html
内容:
<div id="app"></div><script src="/node_modules/@vue/reactivity/dist/reactivity.global.js"></script><script> const {reactive, effect} = VueReactivity; const state = reactive({name:"chenwl",age:12,address:"guangzhou1"}); effect(()=>{ app.innerHTML = `${state.name} 往年 ${state.age} 岁` }); // 当 effect 函数中依赖的数据发生变化 effect 会从新执行 setTimeout(() => { state.name = "change" }, 1000);</script>
外围:当读取文件时,做依赖收集,当数据变动时从新执行effect
初始化目录构造
-src - reactivity - effect.ts - reactive.ts - index.ts - shared - index.ts //通用办法 - index.ts
reactivity/index.ts
export { reactive } from './reactive'export { effect } from './effect'
src/index.ts
export * from "./reactivity/index"
创立响应式对象
reactive/reactive.ts
import { isObject } from "../shared/index"const mutableHandlers = { get(){}, set(){}}export function reactive(target){ // 将指标对象变成响应式对象,Proxy return createReactiveObject(target, mutableHandlers)}// 外围:当读取文件时,做依赖收集,当数据变动时从新执行effectfunction createReactiveObject(target, baseHandlers) { // 如果是不是对象,间接返回 if(!isObject(target)) return target; return new Proxy(target, baseHandlers)}
简略的实现下 isObject
办法:
export const isObject = (val) => typeof val === 'object' && val !== null
Proxy劣势:
Proxy
间接监听对象而非属性,只是对外层对象做代理,默认不会递归,不会重写对象中的属性Proxy
能够间接监听数组的变动Proxy
返回的是一个新对象,咱们能够只操作新的对象达到目标,而 Object.defineProperty 只能遍历对象属性间接批改
创立映射表
为避免对象被反复代理,这里应用WeakMap
创立代理元素的映射表,如果对象被代理过,则间接返回:
// 映射表const proxyMap = new WeakMap()function createReactiveObject(target, baseHandlers) { if (!isObject(target)) return target // 从映射表中取出,判断是否被代理过 const existingProxy = proxyMap.get(target) if (existingProxy) return existingProxy const proxy = new Proxy(target, baseHandlers) // 放入代理对象 proxyMap.set(target, proxy) return proxy}
WeakMap 绝对于 Map 也是键值对汇合,然而 WeakMap 的key 只能是非空对象(non-null object),WeakMap 对它的 key 仅放弃弱援用,也就是说它不阻止垃圾回收器回收它所援用的 key,WeakMap 最大的益处是能够防止内存透露。一个仅被 WeakMap 作为 key 而援用的对象,会被垃圾回收器回收掉。
代理工厂函数
为了方便管理代理逻辑,这里拆分 mutableHandlers
对象到新文件/reactivity/haseHandler.ts
中。
reactivity/haseHandler.ts
function createGetter() { return function get(target, key, reaciver) {}}function createSetter() { return function set(target, key, value, receiver) {}}const get = createGetter();const set = createSetter()export const mutableHandlers = { get, // 获取对象会执行此办法 set, // 设置属性值会执行此办法}
set 和 get 办法通过工厂函数创立,工厂函数的能够不便参数的传参和预置
在 reactive.ts
文件中引入:
import { mutableHandlers } from './baseHandler'export function reactive(target) { // 将指标对象变成响应式对象,Proxy return createReactiveObject(target, mutableHandlers)}
getter 办法
当代理对象的属性被获取时:
function createGetter() { return function get(target, key, reaciver) { const res = Reflect.get(target, key, reaciver) // 相当于 target[key]; // 不对 symbol 类型做解决 if (typeof key === 'symbol') return res; console.log('此时代理对象的属性被获取') // 如果是对象,进行递归代理 if (isObject(res)) return reactive(res); return res }}
setter 办法
在对属性进行设置之前,须要判断是批改值
还是新增值
,并且须要留神,如果是数组,须要判断批改的形式是否通过索引增加:
let arr = [1];arr[10] = 10; // 通过索引新增值的数组
所以判断之前,还须要对target
进行判断,如果是数组,须要减少索引判读。
数组索引比原数组长度小 ? 批改 : 新增
通过target[key]
先获取旧值,而后再跟新值比对判断。
代码逻辑:
function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key] // 获取旧值,看下有没有这个属性 // 如果是数组,依据索引判断是批改还是新增 const hasKey = isArray(target) && isInteger(key) ? Number(key) < target.length : hasOwn(target, key) const result = Reflect.set(target, key, value, receiver); if(!hasKey){ console.log("新增属性"); }else if (hasChanged(value, oldValue)) { console.log('批改属性') } return result }}
通用办法:
export const isArray = Array.isArrayexport const isInteger = (key) => '' + parseInt(key, 10) === keyconst hasOwnProperty = Object.prototype.hasOwnProperty;export const hasOwn = (val, key) => hasOwnProperty.call(val,key);export const hasChanged = (value, oldValue) => value !== oldValue
批改模板 public/index.html
下的援用,能够看到控制台输入对应的属性设置操作。
<script src="../dist/VueReactivity.js"></script><script> const { reactive, effect } = VueReactivity const state = reactive({ name: 'chenwl', age: 12, address: 'guangzhou1' }) state.name = 'change' // 批改属性 state.newProp = 'newProp' // 新增属性 const stateArr = reactive(['a', 'b', 'c']) stateArr[0] = 'array change' // 数组批改 stateArr[3] = 'add array' // 数组新增</script>
effect 函数
回到开始写的 public/index.html
内容:
<div id="app"></div><script src="../dist/VueReactivity.js"></script><script> const { reactive, effect } = VueReactivity const state = reactive({ name: 'chenwl', age: 12 }) effect(()=>{ app.innerHTML = `${state.name} 往年 ${state.age} 岁` }); setTimeout(() => { state.name = 'change' // 批改属性 }, 1000)</script>
页面初始化后,app
标签的内容为 chenwl 往年 12 岁
,一秒后批改为:change 往年 12 岁
。
当代理对象的值产生扭转时,effect
函数参数外面用户自定义的办法也会执行
初始化 effect 函数
下面的逻辑能够失去,effect
办法第一个参数为用户自定义的办法,外面寄存用户本人的逻辑,这个办法在上面的状况下会执行:
- options.lazy 为 false,初始化时执行
- 呈现在自定义函数外面的代理对象产生扭转
批改 effect.ts
如下:
export function effect(fn, options: any = {}) { const effect = createReactiveEffect(fn, options) if (!options.lazy) { effect() } return effect}function createReactiveEffect(fn, options) { const effect = function () { return fn() // 用户本人写的逻辑,外部会对数据进行取值操作 } return effect}
收集依赖
申明变量 activeEffect
存储以后执行的 effect
函数:
let activeEffect; // 用来存储以后的effect函数function createReactiveEffect(fn, options) { const effect = function () { activeEffect = effect return fn() } return effect}
fn 函数执行时,函数上下文的响应式变量会做取值(getter
)操作,此时能够通过activeEffect
获取以后响应式变量关联的effect
// fn函数执行,触发响应式变量`state`的取值操作effect(() => { app.innerHTML = `${state.name} 往年 ${state.age} 岁`})...// baseHandler.tsfunction createGetter(){ return function get(target, key, reaciver) { // 触发取值操作 }}
track依赖收集
为了将响应式属性和effect进行关联,这里申明 track
函数进行依赖收集:
// effect.tsexport function track(target,key){ if(activeEffect === undefined) return;}
当调用fn()
时,会执行用户传入的函数,此时会进行取值操作,咱们在这里实现依赖收集性能:
// baseHandler.ts function createGetter() { return function get(target, key, reaciver) { console.log('此时代理对象的属性被获取') track(target, key) }}
建设映射表,存储 effect
更新函数 和 响应式属性
的关系:
// 映射表const targetMap = new WeakMap(); // targetMap = {target:{key:[effect,effect]}}// 属性和effect关联export function track(target, key) { if (!activeEffect) return let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) }}
let activeEffect; // 用来存储以后的effect函数+let uid = 0;function createReactiveEffect(fn, options) { const effect = function () { activeEffect = effect return fn() }+ effect.id = uid++ // effect标识+ effect.deps = [] // 用来示意 effect 中依赖了哪些属性+ effect.options = options // effect中参数 return effect}
清空 activeEffect
当依赖收集实现,须要清空以后的 activeEffect
办法:
function createReactiveEffect(fn, options) { const effect = function () {+ try { activeEffect = effect return fn()+ } finally {+ activeEffect = null+ } } ... return effect}export function track(target, key) { if (!activeEffect) return; // 不存在或被清空不执行映射关系存储}
然而如果呈现上面的状况:
effect(()=>{ state.name; effect(()=>{ state.age }); state.address})
外部的effect
办法在收集完依赖后,就会清空activeEffect
办法,导致最初的state.address
没有被收集。
栈构造清空,保障清空的是最初一个effect
let activeEffect let uid = 0+ const effectStack = []function createReactiveEffect(fn, options) { const effect = function () { try { activeEffect = effect+ effectStack.push(activeEffect) return fn() } finally {+ effectStack.pop()+ activeEffect = effectStack[effectStack.length - 1] } } effect.id = uid++ effect.deps = [] effect.options = options return effect}
解决死循环:
effect(() => { state.age++ app.innerHTML = `${state.name} 往年 ${state.age} 岁`})
state.age
始终在变动会导致effect一直的递归执行,为避免这种状况,如果effectStack
存储了同样的effect
略过:
const effectStack = []function createReactiveEffect(fn, options) { const effect = function () {+ if (effectStack.includes(effect)) return try {} finally {} } ...}
trigger触发更新
依赖收集后,接下来触发函数更新,这里实现trigger
函数触发更新:
export enum TriggerType { add = 'add', set = 'set',}export function trigger(target, type:TriggerType, key, value?, oldValue?) { const depsMap = targetMap.get(target) if (!depsMap) return const run = (effects) => { if (effects) effects.forEach((effect) => effect()) } if (key != void 0) { run(depsMap.get(key)) }}
设置响应式属性时,触发 trigger
function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key] ... if (!hasKey) { // 新增属性+ trigger(target, TriggerType.add, key, value) } else if (hasChanged(value, oldValue)) { // 批改属性+ trigger(target, TriggerType.set, key, value, oldValue) } ... }}
数组触发的更新
状况一:收集和批改都是数组属性(length)
const state = reactive([1, 2, 3])effect(() => { app.innerHTML = state.length})setTimeout(() => { state.length = 100}, 1000)
后果:触发更新
状况二:批改数组长度,没有收集数组属性
const state = reactive([1, 2, 3])effect(() => { app.innerHTML = state[2]})setTimeout(() => { state.length = 1}, 1000)
后果:属性批改,没有触发更新
批改条件判断:
if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= value) { run(dep) } })} else { if (key != void 0) { run(depsMap.get(key)) }}
状况三:通过索引减少数组选项,收集数组长度小于批改长度
const state = reactive([1, 2, 3])effect(() => { app.innerHTML = state})setTimeout(() => { state[10] = 10}, 1000)
后果:通过索引批改,没有触发更新
增加条件判断:
switch (type) { case 'add': if (isArray(target)) { // 数组通过索引减少选项 if (isInteger(key)) { run(depsMap.get('length')) } }}
残缺的 trigger
函数:
export enum TriggerType { add = 'add', set = 'set',}export function trigger(target, type: TriggerType, key, value?, oldValue?) { const depsMap = targetMap.get(target) if (!depsMap) return const run = (effects) => { if (effects) effects.forEach((effect) => effect()) } if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= value) { run(dep) } }) } else { if (key != void 0) { run(depsMap.get(key)) } switch (type) { case 'add': if (isArray(target)) { // 数组通过索引减少选项 if (isInteger(key)) { run(depsMap.get('length')) } } } }}
响应式过程
通过上面的例子来回顾下vue3响应式执行的过程
const { reactive, effect } = VueReactivity// reactive 办法将参数变成响应式对象const state = reactive({ name: 'chenwl' })// effect 外部如何操作effect(() => { app.innerHTML = `姓名:${state.name}`})setTimeout(() => { state.name = 'change'}, 1000)
首先 reactive 将参数变成响应式对象并返回,接着就是effect函数的执行过程
let activeEffect;const effect = function (fn){ console.log("1、effect 函数执行"); try{ console.log("2、保留以后effect函数到 activeEffect"); activeEffect = effect; console.log("3、fn 函数执行"); fn() }finally{ // 清空 activeEffect }}
fn()
函数执行,state.name
作为响应式属性会进入它的getter
拜访器:
function createGetter() { return function get(target, key, reaciver) { const res = Reflect.get(target, key, reaciver) // 相当于 target[key]; if (typeof key === 'symbol') return res // 不对 symbol 类型做解决 console.log(`4、进入 ${key} => getter 拜访器`) track(target, key) if (isObject(res)) return reactive(res) // 如果是对象,进行递归代理 return res }}
在 getter
拜访器外面,track
会收集以后属性所依赖的effect函数:
const targetMap = new WeakMap()export function track(target, key) { if (activeEffect == undefined) return let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) } // targetMap = { target: { key: [effect, effect] } } console.log(`5、${key} => 收集依赖:`, targetMap)}
state.name
产生批改操作,进入到响应式属性的设置办法并触发trigger
更新办法:
function createSetter() { return function set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver) console.log(`6、${key} => 批改属性`) trigger(target, TriggerType.set, key, value, oldValue) return result }}
trigger
办法外面找到targetMap
对应的target.key
,获取以后响应式属性所在的effect函数并执行更新操作
export function trigger(target, type: TriggerType, key, value?, oldValue?) { console.log(`8、${key} => 触发更新`) const run = (effects=[]) => { effects.forEach(effect=>{ console.log(`9、获取${key} => targetMap的effect执行`) console.log('===== 进入key存储的effect =====') effect(); }) } if (key != void 0) { run(depsMap.get(key)) } }
控制台打印后果:
1、effect2、保留以后effect函数到activeEffect3、fn函数执行4、进入 name => getter 拜访器5、name => 收集依赖: WeakMap {{…} => Map(1)}6、name => 批改属性7、name => 触发更新8、获取name => targetMap的effect执行===== 进入key存储的effect =====1、effect2、保留以后effect函数到activeEffect3、fn函数执行4、进入 name => getter 拜访器5、name => 收集依赖: WeakMap {{…} => Map(1)}
计算属性 Computed
computed 应用
计算属性 computed
的应用:
<div id="app"></div><script src="/node_modules/@vue/reactivity/dist/reactivity.global.js"></script><script> const { reactive, effect, computed } = VueReactivity const state = reactive({ name: 'chenwl', age: 12 }) const birth_year = computed(() => { return new Date().getFullYear() - state.age }) effect(() => { app.innerHTML = `${state.name} 出生于 ${birth_year.value} 年` }) setTimeout(() => { state.age++ }, 1000)</script>
当 state.age
的值发生变化时,依赖于它的 birth_year
会从新执行计算属性。
computed 返回值
通过打印 birth_year
能够在控制台看到它的值:
ComputedRefImpl = { __v_isReadonly: true, __v_isRef: true, _dirty: true, setter: ƒ, effect: ƒ, _value: 2008, value: 2008,}
默认计算属性的值被包装到了value属性上
初始化 computed
新建 reactivity/computed.ts
并导出:
export function computed(getterOrOptions) { let getter let setter if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = () => console.warn('computed not set value') } else { getter = getterOrOptions.get setter = getterOrOptions.set }}
computed
接管一个参数,这个参数可能是函数也可能是 {getter,setter}
对象,初始化函数并对参数进行判断。
ComputedRefImpl 类
通过下面 birth_year
的打印后果,能够发现计算属性返回的是一个 ComputedRefImpl
实例,所以申明 ComputedRefImpl
类:
import { effect } from './effect.ts'class ComputedRefImpl { public effect constructor(getter, setter) { // 默认 getter 执行时会依赖于 effect(计算属性默认是effect) this.effect = effect(getter, { lazy: true, // 默认初始化不执行 }) }}export function computed(getterOrOptions) { let getter let setter if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = () => console.log('computed not set value') } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl(getter, setter)}
申明 ComputedRefImpl
类的公共属性和value
属性的拜访器 get
和设置 set
:
import { effect, track } from './effect.ts'class ComputedRefImpl { public effect public __v_isReadonly = true public __v_isRef = true // 判断是否间接返回 value 值 public _dirty = true // 缓存数据 private _value constructor(getter, public setter) { // 默认 getter 执行时会依赖于 effect(计算属性默认是effect) this.effect = effect(getter, { lazy: true, }) } get value() { // 当计算属性执行时,收集计算属性的 effect this._value = this.effect() return this._value } set value(newValue) { this.setter(newValue) }}
计算属性的依赖收集和scheduler
剖析
计算属性外面的响应式属性一旦被批改,须要告诉计算属性所在的effect函数做出更新操作:
如下,计算属性 birth_year
外面蕴含响应式属性 state.age
:
const birth_year = computed(()=>{ return new Date().getFullYear() - state.age})
当state.age
做出批改操作:
setTimeout(() => { state.age++}, 1000);
告诉birth_year
所在的effect
函数做出更新操作:
effect(() => { app.innerHTML = `出生于 ${birth_year.value} 年`})
逻辑实现:
1、首先第一个 effect 办法开始执行,产生 activeEffect
并存储在 stackEffects
数组中:
const stackEffects = [activeEffect]
这里的 activeEffect
等于上面的办法:
effect1 = () => { app.innerHTML = `出生于 ${birth_year.value} 年`}
也就是:
const stackEffects = [effect1]
2、接下来会进入计算属性 birth_year
的拜访器 value
办法,须要返回计算属性的执行后果:
get value(){ return new Date().getFullYear() - state.age}
为了记录以后计算属性所依赖的 effect
函数,批改ComputedRefImpl
如下:
private _value;constructor(getter, public setter) { this.effect = effect(getter, {lazy: true})}get value() { this._value = this.effect() return this._value}
effect
办法的执行存储了以后计算属性所在的 activeEffect
,当初stackEffects
数组保留了两个 activeEffect
:
const stackEffects = [effect1,effect2]
effect2
实际上是计算属性的办法:
effect2 = ()=>{ return new Date().getFullYear() - state.age}
第一个 activeEffect 来自更新内容的 effect 函数,第二个 activeEffect 来自 computed
3、this.effect()
办法执行后,进入到state.age
属性拜访器进行依赖收集,这里通过targetMap
映射表会将state.age
和 activeEffect
进行关联:
targetMap = {state: { age: effect2 } }
4、关联后的 state.age
的产生更新操作,触发 effect2
函数的从新执行,上面是 effect1
和 effect2
对应的函数:
effect1 = () => app.innerHTML = `出生于 ${birth_year.value} 年`;effect2 = ()=> new Date().getFullYear() - state.age;
5、计算属性冀望的是 state.age
的更新可能触发 effect1
的从新执行,所以在获取计算属性时,须要进行依赖收集:
get value() { this._value = this.effect()+ track(this, 'value') return this._value}
track
的执行让 targetMap
外面映射表变成了上面这样:
const targetMap = { state: { age: effect2 }, ComputedRefImpl: { value: effect1 }}
6、为了让 state.age
的更新可能触发 effect1
的从新执行,批改构effect的options选项,新增scheduler
办法:
constructor(getter, public setter) { this.effect = effect(getter, { lazy: true, // lazy=true 默认不会执行 scheduler: () => { trigger(this, TriggerType.set, 'value') }, })}
批改effect.ts
外面的 trigger
办法:
function trigger(){... const run = (effects=[]) => { effects.forEach((effect) => { if(effect.options.scheduler){ effect.options.scheduler(effect) }else{ effect(); } }) } ...}
当 effect
有 scheduler
属性办法时,执行 scheduler
办法,也就是 state.age
的批改会执行上面的逻辑:
trigger(this, TriggerType.set, 'value')
这个触发更新等于触发了 effect1
办法的从新执行:
effect1 = () => app.innerHTML = `出生于 ${birth_year.value} 年`;
执行过程
computed 的执行:
1、state.age
更新触发了 birth_year
的 computed effect 函数
2、computed effect
执行计算属性的 scheduler
办法
3、scheduler
触发了 birth_year.value
所在的 effect 函数更新
state.age => computed effect => scheduler => effect函数(birth_year.value)
残缺的 computed
办法:
import { effect, track, trigger, TriggerType } from './effect.ts'class ComputedRefImpl { public effect public __v_isReadonly = true public __v_isRef = true // 判断是否间接返回 value 值 public _dirty = true // 缓存数据 private _value constructor(getter, public setter) { // 默认 getter 执行时会依赖于 effect(计算属性默认是effect) this.effect = effect(getter, { lazy: true,+ scheduler: () => {+ trigger(this, TriggerType.set, 'value')+ }, }) } get value() { // 当计算属性执行时,收集计算属性的 effect this._value = this.effect() // 收集计算属性外面的依赖+ track(this, 'value') return this._value } set value(newValue) { this.setter(newValue) }}
依赖缓存
当批改跟计算属性没有关联的state.name
时,能够看到birth_year
的effect
也会被执行:
const { reactive, effect, computed } = VueReactivityconst state = reactive({ name: 'chenwl', age: 12 })const birth_year = computed(() => {+ console.log('computed execute') return new Date().getFullYear() - state.age})effect(() => { app.innerHTML = `${state.name} 出生于 ${birth_year.value} 年`})setTimeout(() => {+ state.name = 'change'}, 1000)
state.name
产生扭转,控制台打印出 'computed execute'
当state.name
所在的effect
函数执行时,birth_year.value
的属性拜访器也会被触发,收集依赖并执行计算属性的effect
函数。
批改计算属性的value拜访器,依据后面申明的公共属性this._dirty
,判断以后_dirty
(脏值)是否为 true
来决定是否收集依赖和从新获取新值:
get value() { if (this._dirty) { this._value = this.effect() track(this, 'value') this._dirty = false } return this._value}
当scheduler
函数被执行时,阐明值被批改,须要从新设置_dirty
:
constructor(getter, public setter) { this.effect = effect(getter, { lazy: true, scheduler: () => {+ this._dirty = true trigger(this, TriggerType.set, 'value') }, })}
残缺的 computed.ts
:
import { isFunction } from '../shared/index'import { effect, track, trigger, TriggerType } from './effect'class ComputedRefImpl { public effect public __v_isReadonly = true public __v_isRef = true // 判断是否间接返回 value 值 public _dirty = true // 缓存数据 private _value constructor(getter, public setter) { this.effect = effect(getter, { lazy: true, // lazy=true 默认不会执行 scheduler: () => { this._dirty = true trigger(this, TriggerType.set, 'value') }, }) } get value() { if (this._dirty) { this._value = this.effect() track(this, 'value') this._dirty = false } return this._value } set value(newValue) { this.setter(newValue) }}export function computed(getterOrOptions) { let getter let setter if (isFunction(getterOrOptions)) { getter = getterOrOptions setter = () => console.log('computed not set value') } else { getter = getterOrOptions.get setter = getterOrOptions.set } return new ComputedRefImpl(getter, setter)}
Ref
ref的实现,判断传入值是不是对象,对象用reactive
包裹解决,获取值时收集依赖,设置值时触发更新
import { hasChanged, isObject } from "../shared/index";import { track, trigger, TriggerType } from "./effect";import { reactive } from "./reactive";const convert = (val) => isObject(val) ? reactive(val) : val;class RefImpl { public readonly __v_isRef = true; private _value; constructor(private _rawValue){ this._value = convert(_rawValue) } get value(){ track(this,"value") return this._value } set value(newValue){ if (hasChanged(newValue, this._rawValue)) { this._rawValue = newValue; this._value = convert(newValue); trigger(this, TriggerType.set, "value"); } }}export function ref(rawValue){ return new RefImpl(rawValue)}