大家好,我是 Mokou,最近始终在做 vue3 相干内容,比方源码解析和 mini-vue3 的开发。
回顾下前几章的内容,在前几章中次要讲述了以下内容。
- 新构建工具
vite
的原理和从零开始实现 vue3
应用新姿态- 新 api:
reactive
应用和源码解析 - 追踪收集
track
实现和源码解析 - 追踪触发器
trigger
实现和源码解析 - 响应式外围
effect
与track、trigger
工作原理和源码解析
好的,这章的指标:从零开始实现一个 Vue3!
必须要晓得的前置常识 effect
与 track、trigger
工作原理,具体详情请看公众号 -> 前端进阶课
,一个有温度且没有广告的前端技术公众号。
在这里还是简略解析下这 3 个函数的作用吧
- track: 收集依赖,存入
targetMap
- trigger:触发依赖,应用
targetMap
- effect:副作用解决
本章源码请看 uuz 急需 star 维持生计。
手摸手实现 Vue3
首先。咱们 2 个全局变量,用来寄存和定位追踪的依赖,也就是给 track
和 trigger
应用的仓库。
let targetMap = new WeakMap();
let activeEffect;
所以第一个须要设计的办法就是 track
,还记得该 track
在 vue3 是如何调用的吗?
track(obj, 'get', 'x');
track
会去找 obj.x
是否被追踪,如果没找到就将 obj.x 放入targetMap
(实现追踪工作),将 obj.x
作为 map 的 key 将 activeEffect 作为 map 的 value。
抛开取值异样解决之类的,track
只做了一件事,将 activeEffect
塞入targetMap
;
function track(target, key) {
// 首先找 obj 是否有被追踪
let depsMap = targetMap.get(target);
if (!depsMap) {
// 如果没有被追踪,那么增加一个
targetMap.set(target, (depsMap = new Map()));
}
// 而后寻找 obj.x 是否被追踪
let dep = depsMap.get(key);
if (!dep) {
// 如果没有被追踪,那么增加一个
depsMap.set(key, (dep = new Set()));
}
// 如果没有增加 activeEffect 那么增加一个
if (!dep.has(activeEffect)) {dep.add(activeEffect);
}
}
而后就是写一个 trigger
,还记得 trigger
在 vue 是如何调用的吗?
trigger(obj, 'set', 'x')
trigger
只会去 targetMap
中寻找 obj.x
的追踪工作,如果找到了就去重,而后执行工作。
也就是说:抛开取值异样相干,trigger
也只做了一件事:从 targetMap
取值而后调用该函数值。
function trigger(target, key) {
// 寻找追踪项
const depsMap = targetMap.get(target);
// 没找到就什么都不干
if (!depsMap) return;
// 去重
const effects = new Set()
depsMap.get(key).forEach(e => effects.add(e))
// 执行
effects.forEach(e => e())
}
最初就是 effect
,还记得该打工仔的 api 在 vue3 中是如何调用的吗?
effect(() => {console.log('run cb')
})
effect
接管一个回调函数,而后会被送给 track
。所以咱们能够这么实现 effect
- 定义一个外部函数
_effect
,并执行。 - 返回一个闭包
而外部 _effect
也做了两件事
- 将本身赋值给
activeEffect
- 执行
effect
回调函数
优良的代码跃然纸上。
function effect(fn) {
// 定义一个外部 _effect
const _effect = function(...args) {
// 在执行是将本身赋值给 activeEffect
activeEffect = _effect;
// 执行回调
return fn(...args);
};
_effect();
// 返回闭包
return _effect;
}
所有的前置项都实现了,当初开始实现一个 reactive
,也就是对象式响应式的 api。还记得 vue3 中如何应用 reactive
吗?
<template>
<button @click="appendName">{{author.name}}</button>
</template>
setup() {
const author = reactive({name: 'mokou',})
const appendName = () => author.name += '优良';
return {author, appendName};
}
通过下面的的优良代码,很轻易的实现了 vue3 的响应式操作。通过回顾前几章的内容,咱们晓得 reactive
是通过 Proxy 代理数据实现的。
这样咱们就能够通过 Proxy
来调用 track
和 trigger
,劫持 getter
和 setter
实现响应式设计
export function reactive(target) {
// 代理数据
return new Proxy(target, {get(target, prop) {
// 执行追踪
track(target, prop);
return Reflect.get(target, prop);
},
set(target, prop, newVal) {Reflect.set(target, prop, newVal);
// 触发 effect
trigger(target, prop);
return true;
}
})
}
好了。所有就绪,那么咱们挂载下咱们的 fake vue3
吧
export function mount(instance, el) {effect(function() {instance.$data && update(el, instance);
})
instance.$data = instance.setup();
update(el, instance);
}
function update(el, instance) {el.innerHTML = instance.render()
}
用 mini-vue3 写一个 demo
测试一下。参照 vue3 的写法。定义个 setup
和 render
。
const App = {
$data: null,
setup () {let count = reactive({ num: 0})
setInterval(() => {count.num += 1;}, 1000);
return {count};
},
render() {return `<button>${this.$data.count.num}</button>`
}
}
mount(App, document.body)
执行一下,果然是优良的代码。响应式失常执行,每次 setInterval
执行后,页面都重写刷新了 count.num
的数据。
源码请看 uuz,ps:7 月 23 日该源码曾经反对 jsx 了。
以上通过 50+
行代码,轻轻松松的实现了 vue3
的响应式。但这就完结了吗?
还有以下问题
Proxy
肯定须要传入对象render
函数 和h
函数并正确(Vue3 的 h 函数当初是 2 个不是以前的createElement
了)- 虚构 dom 的递归
- 别再说了
- -!
,我不听。
ref
应用 reactive 会有一个毛病,那就是,Proxy 只能代理对象,但不能代理根底类型。
如果你调用这段代码 new Proxy(0, {})
,浏览器会反馈你 Uncaught TypeError: Cannot create proxy with a non-object as target or handler
所以,对于根底类型的代理。咱们须要一个新的形式,而在 vue3
中,对于根底类型的新 api 是 ref
<button >{{count}}</button>
export default {setup() {const count = ref(0);
return {count};
}
}
实现 ref 其实非常简单:利用 js 对象自带的 getter 就能够实现
举个栗子:
let v = 0;
let ref = {get value() {console.log('get')
return v;
},
set value(val) {console.log('set', val)
v= val;
}
}
ref.value; // 打印 get
ref.value = 3; // 打印 set
那么通过后面几章实现的 track
和 trigger
能够轻松实现 ref
间接上实现的代码
function ref(target) {
let value = target
const obj = {get value() {track(obj, 'value');
return value;
},
set value(newVal) {if (newVal !== value) {
value = newVal;
trigger(obj, 'value');
}
}
}
return obj;
}
computed
那么该怎么实现 computed
?
首先:参考 vue3
的 computed
应用形式
let sum = computed(() => {return count.num + num.value + '!'})
盲猜能够失去一个想法,通过革新下 effect
能够实现,即在 effect
调用的那一刻不执行 run
办法。所以咱们能够加一个 lazy
参数。
function effect(fn, options = {}) {const _effect = function(...args) {
activeEffect = _effect;
return fn(...args);
};
// 增加这段代码
if (!options.lazy) {_effect();
}
return _effect;
}
那么 computed
能够这么写
- 外部执行
effect(fn, {lazy: true})
保障computed
执行的时候不触发回调。 - 通过对象的
getter
属性,在computed
被应用的时候执行回调。 - 通过
dirty
防止出现内存溢出。
优良的代码跃然纸上:
function computed(fn) {
let dirty = true;
let value;
let _computed;
const runner = effect(fn, {lazy: true});
_computed = {get value() {if (dirty) {value = runner();
dirty = false;
}
return value;
}
}
return _computed;
}
那么问题来了 dirty
在第一次执行后就被设置为 false
如何重置?
此时 vue3
的解决办法是,给 effect
增加一个 scheduler
用来解决副作用。
function effect(fn, options = {}) {const _effect = function(...args) {
activeEffect = _effect;
return fn(...args);
};
if (!options.lazy) {_effect();
}
// 增加这行
_effect.options = options;
return _effect;
}
既然有了 scheduler
那就须要更改 trigger
来解决新的 scheduler
。
function trigger(target, key) {const depsMap = targetMap.get(target);
if (!depsMap) return;
const effects = new Set()
depsMap.get(key).forEach(e => effects.add(e))
// 更改这一行
effects.forEach(e => scheduleRun(e))
}
// 增加一个办法
function scheduleRun(effect) {if (effect.options.scheduler !== void 0) {effect.options.scheduler(effect);
} else {effect();
}
}
而后,把下面代码合并一下,computed
就实现了
function computed(fn) {
let dirty = true;
let value;
let _computed;
const runner = effect(fn, {
lazy: true,
scheduler: (e) => {if (!dirty) {
dirty = true;
trigger(_computed, 'value');
}
}
});
_computed = {get value() {if (dirty) {value = runner();
dirty = false;
}
track(_computed, 'value');
return value;
}
}
return _computed;
}
总结
- reactive 的外围是
track
+trigger
+Proxy
- ref 是通过对象自有的
getter
和setter
配合track
+trigger
实现的 - computed 其实是一个在
effect
根底上的改良
下章内容:vue3
该怎么联合 jsx
?
最初
原创不易,给个三连刺激下弟弟吧。
- 源码请看 uuz
- 本文内容出自 https://github.com/zhongmeizhi/FED-note
- 欢送关注公众号「前端进阶课」认真学前端,一起进阶。回复
全栈
或Vue
有好礼相送哦