1、vue3 编译过程
template ==> ast ===> 通过 render
模板 ==> 抽象语法树 ==> render函数
调用栈
目录位置:vue-next/packages/runtime-core/src/component.ts
mountComponent ---> setupComponent ---> finishComponentSetup
mountComponent 简化流程
目录:packages\runtime-core\src\renderer.ts
// 1.创建实例
const instance = createComponentInstance(...) // 创建组件实例
// ....
// 2组件安装 类似于 vue2_init 合并merge options,// 属性初始化 // 数据响应式 // 插槽处理
setupComponent(instance)
// ....
// 3、 安装渲染函数的副作用
setupRenderEffect()
在 finishComponent 方法内
// ...
if (compile && Component.template && !Component.render) {
if (__DEV__) {
startMeasure(instance, `compile`)
}
// 只有global版本带编译版本才会有这一步走
//(断点打到这里一步一步走下去看)
Component.render = compile(Component.template, { // 编译template生成render函数
isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
})
if (__DEV__) {
endMeasure(instance, `compile`)
}
}
// ....
关于 dev-compiler 命令 (查看vue3的编译优化)
npm run dev
npm run dev-compiler
打开localhost:5000
进入 packages/template-explorer 目录
可以看到 模板编译
1、静态节点提示
以下为 代码生成字符串
const _Vue = Vue
const { createVNode: _createVNode } = _Vue
// 这里就是静态提升 内存换时间
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "1", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "2", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "3", -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/_createVNode("p", null, "4", -1 /* HOISTED */)
return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createVNode: _createVNode, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock } = _Vue
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("p", null, _toDisplayString(counter), 1 /* TEXT */),
_hoisted_1,
_hoisted_2,
_hoisted_3,
_hoisted_4
], 64 /* STABLE_FRAGMENT */))
}
}
2、 补丁标记和动态属性记录
模板
<div :title='hello'>Hello World!</div>
<p>LALA~~</p>
import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "LALA~~", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", { title: _ctx.hello }, "Hello World!", 8 /* PROPS 这里就是patchFlag */, ["title"]),
_hoisted_1
], 64 /* STABLE_FRAGMENT */))
}
patchFlag 为 8 表示只有属性需要更新 0b1000
patchFlag 为 1 表示只有动态文本 0b0001
patchFlag 为 9 表示有属性和动态文本需要 patch 更新比较 0b10011
3、关于事件 有 cacheHandlers 把事件缓存起来,避免重复创建
替换template为
<div :title='hello'>Hello World!</div>
<p @click="onclick">LALA~~</p>
import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", { title: _ctx.hello }, "Hello World!", 8 /* PROPS */, ["title"]),
_createVNode("p", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onclick(...args))) // 事件会保存到_cache[1]
}, "LALA~~")
], 64 /* STABLE_FRAGMENT */))
}
4、代码区块block 有动态节点会保存起来(用数组) 遍历的时候进行比较
创建区块
_createBlock(_Fragment, null, [
_createVNode("div", { title: _ctx.hello }, "Hello World!", 8 /* PROPS */, ["title"]),
_createVNode("p", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onclick(...args))) // 事件会保存到_cache[1]
}, "LALA~~")
], 64 /* STABLE_FRAGMENT */))
}
// 在 虚拟节点vnode属性 dynamicChildren (数组)保存动态节点
vue3异步更新策略
起点位置:
组件的更新方法是个effect一个update函数
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// create reactive effect for rendering 更新函数
instance.update = effect(function componentEffect() {
//....
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
const prodEffectOptions = {
scheduler: queueJob, //其实是在这里影响了
// #1801, #2043 component render effects should allow recursive updates
allowRecurse: true
}
1、关于effect方法
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
这里没找到options里 scheduler 的影响
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options // 这里放置了options
return effect
}
关于trigger 触发更新
发现trigger用到了scheduler
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) { // 发现在这里
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
2、关于 执行更新任务
目录位置:packages\runtime-core\src\scheduler.ts
export function queueJob(job: SchedulerJob) {
// the dedupe search uses the startIndex argument of Array.includes()
// by default the search index includes the current job that is being run
// so it cannot recursively trigger itself again.
// if the job is a watch() callback, the search will start with a +1 index to
// allow it recursively trigger itself - it is the user's responsibility to
// ensure it doesn't end up in an infinite loop.
if (
(!queue.length ||
!queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)) &&
job !== currentPreFlushParentJob
) {
queue.push(job) // 把effect的函数push到 queue
queueFlush()
}
}
function queueFlush() { // flushJobs 是 执行queue里存的effect函数
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
vue3 patch 过程
1、首先vnode上的变化: children可以数组也可以是文本
2、dynamicChildren 、 dynamicProps 记录动态节点和属性比对,减少遍历操作
3、patchFlag 标注动态内容 目录位置:packages\shared\src\patchFlags.ts
4、shapeFlag 标记组件形态(是否是组件或teleport)
shapeFlag
export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1, // 2 函数组件
STATEFUL_COMPONENT = 1 << 2, // 4 状态组件
TEXT_CHILDREN = 1 << 3, // 8 文本孩子
ARRAY_CHILDREN = 1 << 4, // 16 数组孩子
SLOTS_CHILDREN = 1 << 5, // 32 插槽孩子
TELEPORT = 1 << 6, // 传送
SUSPENSE = 1 << 7, // 悬念
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 应该缓存的组件
COMPONENT_KEPT_ALIVE = 1 << 9, // 被缓存的组件
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT
}
关于patchKeyChildren diff算法
packages\runtime-core\src\renderer.ts
1、掐头
2、掐尾
3、新的有多直接插入
4、旧的有多直接删
5、按旧的(方式)遍历一个一个找