基于Vue3.0发布在GitHub上的初版源码(2019.10.05)整理html
先把vue-next仓库的代码clone下来,安装依赖而后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本便可。 下面一个简单计数器的DEMO:vue
<!DOCTYPE html>
<html lang="en">
<body>
<div id='app'></div>
</body>
<script src="./dist/vue.global.js"></script>
<script> const { createApp, reactive, computed } = Vue; const RootComponent = { template: ` <button @click="increment"> Count is: {{ state.count }} </button> `, setup() { const state = reactive({ count: 0, }) function increment() { state.count++ } return { state, increment } } } createApp().mount(RootComponent, '#app') </script>
</html>
复制代码
template
和以前同样,一样Vue3也支持手写render
的写法,template
和render
同时存在的状况,优先render
。react
setup
选项是新增的主要变更,顾名思义,setup
函数会在组件挂载前(beforeCreate
和created
生命周期之间)运行一次,相似组件初始化的做用,setup
须要返回一个对象或者函数。返回对象会被赋值给组件实例的renderContext
,在组件的模板做用域能够被访问到,相似data的返回值。返回函数会被当作是组件的render
。具体能够细看文档。git
reactive
的做用是将对象包装成响应式对象,经过Proxy代理后的对象。github
上面的计数器的例子,在组件的setup
函数中,建立了一个响应式对象state
包含一个count
属性。而后建立了一个increment
递增的函数,最后将state
和increment
返回给做用域,这样template
里的button
按钮就能访问到increment
函数绑定到点击的回调,count
也能显示在按钮上。咱们点击按钮,按钮上的数值就能跟着递增。web
下面切入正题,咱们就来探究下按钮上count
值跟着响应式更新的原理api
首先列一下主要的一些数据结构,先列在这里,后面提到能够翻回来看看。缓存
ReactiveEffect
一个Function
对象,用于执行组件的挂载和更新。数据结构
interface ReactiveEffect {
(): any
isEffect: true
active: boolean
raw: Function // 具体执行的函数
deps: Array<Dep>
computed?: boolean
scheduler?: (run: Function) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
}
复制代码
targetMap
相似 {target -> key -> dep}
的一个Map结构,用于缓存全部响应式对象和依赖收集。app
export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<string | symbol, Dep>
export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
复制代码
reactive
函数执行,会将传入的target对象经过Proxy
包装,拦截它的get
,set
等,并将代理的target缓存到targetMap
,targetMap.set(target, new Map())
。
代理的get
的时候会调用一个track
函数,而set
会调用一个triger
函数。分别对应依赖收集和触发更新。
// Proxy get 简化
function get(target: any, key: string | symbol, receiver: any) {
// 经过key拿到原始值res
const res = Reflect.get(target, key, receiver)
// 过滤不须要代理的状况
// ...
// 依赖收集
track(target, OperationTypes.GET, key)
// 若是取到的值是个对象,将对象再代理包装一下
// Proxy只能代理对象第一层级
return isObject(res) ? reactive(res) : res
}
// Proxy set 简化
function set( target: any, key: string | symbol, value: any, receiver: any ): boolean {
// 一些不须要代理设置的场景
// ...
// 设置原始对象的值
const result = Reflect.set(target, key, value, receiver)
// 避免重复trigger的逻辑
// ...
// 触发通知更新
trigger(target, '更新的类型, 新增key或更新key', key)
return result
}
复制代码
组件在render
阶段,视图会读取数据对象上的值进行渲染,此时便触发了Proxy
的get
,由此触发对应的track
函数,记录下了对应的ReactiveEffect
,也就是常说的依赖收集。 ReactiveEffect
其实就能够看做是组件的更新(mount是特殊的update),数据的变动触发trigger
,trigger
遍历调用track
收集的对应的数据的ReactiveEffect
,也就是对应有关联的组件的更新。
trigger
触发的组件的更新,在render
阶段又触发了新一轮的track
依赖收集,更新依赖。
// 简化的 track
function track( target: any, type: OperationTypes, key?: string | symbol ) {
// 只有在依赖收集阶段才进行依赖收集
// 除了render,其余场景也可能会触发Proxy的get,但不须要进行依赖收集
// activeReactiveEffectStack栈顶包装了当前render的组件的mount和update的逻辑
const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]
// 若是effect为空,说明当前不在render阶段
if (effect) {
// ...
// =====>初始化对应{target -> key -> dep}的结构
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key as string | symbol)
if (!dep) {
depsMap.set(key as string | symbol, (dep = new Set()))
}
// <=====初始化对应{target -> key -> dep}的结构
// 依赖列表里若是没有,add
if (!dep.has(effect)) {
// 这里将effect做为依赖,缓存到依赖列表
dep.add(effect)
effect.deps.push(dep)
}
}
}
// 简化的trigger
function trigger( target: any, type: OperationTypes, key?: string | symbol, extraInfo?: any ) {
// 获取对应target在track过程当中缓存的依赖
const depsMap = targetMap.get(target)
const effects: Set<ReactiveEffect> = new Set()
// 省略分类逻辑
depsMap.forEach(dep => {
// 将effect分类过滤添加到effects
})
const run = (effect: ReactiveEffect) => {
// 有个异步调度的过程,nextTick
scheduleRun(effect, target, type, key, extraInfo)
}
effects.forEach(run)
}
复制代码
大体流程:
如今的代码只有新特性的实现,并且ES6+TS的组合可读性大大提升,编辑器支持也很好,因此相对会好读不少。这里只是简单的理了一下vue 3.0 reactive的总体流程,细节还有不少地方值得学习,继续加油。
/** PS:咱们团队还缺人,有想换工做的小伙伴能够发简历给我哦, base深圳,funkyfang(艾特)webank.com。 */
复制代码