本文是我新开的坑的第一篇文章,这个坑就是vue3,接下来我会围绕着vue3进行一系列的动作,包含但不限于:
- 残缺的源码解析
- jsx工程最佳实际
- 个性化的jsx计划
- vue3生态库开发(目前有一个正在进行)
- 以及可能的自定义一个vue3的runtime
对于源码解析,网站曾经上线,vue3源码解析,最佳实际,网站是逐行代码模式的解析,更多关注于源码,而在掘金上分享的文章则相似于总结,会用更复合一篇文章的构造来写。如果你想继续跟进vue3源码,能够关上后面的网站关注我。
那么,开始!
vue3最大的变动莫过于其对于响应式原理的重构,以及其新公布的composition api
,本文聚焦于前者,来深度分析一下vue3中响应式到底是怎么实现的。
咱们以reactive
API 为例,
const Comp = { setup() { const state = reactive({ a: 'jokcy' }) return () => { return <input value={state.a} onChange={(e) => state.a = e.targent.value} /> } }}
咱们看下面的例子,这个例子很简略,创立了一个组件,他有一个响应式的数据对象,而后render外面的input
的value绑定的是state.a
以及他的onChange
会批改state.a
。这是非常简单且直观的一个数据绑定的例子,而这个逻辑能实现的根本原因,是咱们在调用state.a = xxx
的时候,vue会从新渲染咱们return
的render函数,来更新节点
而篇文章就是要来看一下,咱们通过reactive
创立的对象,到底有什么魔力,可能帮咱们实现这个工作。
其实自身 API 是很简略的,传入一个对象,返回一个 reactive 对象,创立的办法是createReactiveObject
export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target; } return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers );}
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>) { // 后面都是一些对象是否曾经proxy等的判断逻辑,这里就不展现了 const observed = new Proxy( target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers ); def( target, isReadonly ? ReactiveFlags.READONLY : ReactiveFlags.REACTIVE, observed ); return observed;}
那么这里最重要的就是new Proxy
了,能够说了解 vue3 的响应式原理过程就是了解这个proxy
创立的过程,而理解这个过程,次要就是看第二个参数,在这里就是collectionHandlers
或者baseHandlers
,大部分是后者,前者次要针对,Set、Map 等。
那么咱们就来看baseHandlers
:
export const mutableHandlers: ProxyHandler<object> = { get, set, deleteProperty, has, ownKeys,};
可见 vue 代理了这几个操作,那么咱们一个个看这几个操作做了啥:
function get(target: object, key: string | symbol, receiver: object) { // ...外部key的货足 // 对于数组的一些非凡解决 const targetIsArray = isArray(target); if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver); } // 获取申请值 const res = Reflect.get(target, key, receiver); // ...如果是外部值的获取则间接返回res if (!isReadonly) { track(target, TrackOpTypes.GET, key); } // 返回的一些解决 return res;}
这里的重点就在于track(target, TrackOpTypes.GET, key);
,这个是咱们失常获取值的时候都会执行到的代码:
export function track(target: object, type: TrackOpTypes, key: unknown) { if (!shouldTrack || 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); if (__DEV__ && activeEffect.options.onTrack) { activeEffect.options.onTrack({ effect: activeEffect, target, type, key, }); } }}
好了,重点来了,咱们逐行剖析。
第一个if
,就是依据环境变量的shouldTrack
来判断是否要进行跟踪,如果你曾经看过我的源码解析中的processComponent
的章节,那么你当初应该就是恍然大悟的感觉。因为在执行processComponent
外面的setup
的时候,咱们顺便敞开了track
,而那时候就是把shouldTrack
改为了false
。
let depsMap = targetMap.get(target)
这行,targetMap
是一个 map,用来记录所有的响应对象。之后如果目前没有记录该对象,那么就从新记录。
这个 target 对应的也是一个 map,他会对每个 key 建设一个 set。
最初要记录这次的 effect 了,这里所谓的effect
是什么呢?就是以后正在调用这个对象的函数。在咱们的例子外面,就是return回去的render
函数,这个函数在执行的时候会调用state.a
所以会进入proxy
对于get
的代理,这个时候 proxy 就调用了track
,那么这时候的activeEffect
就是这个 render 办法。这里的effect
就是,当state.a
改变的时候,咱们须要从新执行该 render 办法来进行渲染。
那么他是什么时候被设置的呢?在mount
章节的时候咱们提到了,在执行render
办法的时候,咱们执行这样一句代码instance.update = effect(function componentEffect()...)
,就是在这里调用的effect
办法外面,把activeEffect
记录为componentEffect
,而这个componentEffect
外面则运行了render
办法。
另外这个getHandler
外面有句代码也挺有意思的:
if (isObject(res)) { return isReadonly ? readonly(res) : reactive(res);}
在获取属性的时候,如果返回的值是对象的时候,才会对其执行reactive
,这也就是提早解决,而且readonly
的话是不执行reactive
的。
OK,到这里咱们曾经晓得了在执行render
函数的时候,因为咱们调用了state.a
所以这个函数就相当于依赖state.a
,这在vue3外面被称为effect
。
那么下一篇文章,咱们就来讲一讲,这些effect
在state.a
变动的时候是如何被调用的,敬请期待。