本文基于Vue 3.2.30
版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变部分分支条件的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,不利于展示,大部分伪代码会取编译后的js代码,而不是原来的ts代码
文章内容
本文仅针对有状态组件、同步逻辑进行分析,不分析异步相关逻辑和函数组件等内容,相关源码展示也会剔除异步相关逻辑和函数组件等内容
- 流程图展示Vue3首次渲染整体流程
- 以流程图为基础,进行首次渲染整体流程的源码分析
- 流程图展示Vue3组件更新整体流程
- 以流程图为基础,进行组件更新整体流程的源码分析
由于本文篇幅较长,按照流程图看源码分析效果更佳
创建vnode
render()函数
- 使用
createBaseVNode
进行普通元素节点的vnode
创建 - 使用
createVNode
进行Component组件
节点的vnode
创建
<template></template>
内容最终会被编译成为render()
函数,如下面代码块所示,render()
函数中执行createBaseVNode(_createElementVNode)
/createVNode(_createVNode)
返回vnode数据
function render(_ctx, _cache) {
with (_ctx) {
const {createElementVNode: _createElementVNode, resolveComponent: _resolveComponent, createVNode: _createVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock} = _Vue;
const _component_InnerComponent = _resolveComponent("InnerComponent");
const _component_second_component = _resolveComponent("second-component");
return (
_openBlock(),
_createElementBlock("div", _hoisted_1, [
_createElementVNode("div", _hoisted_2, [_hoisted_3, _hoisted_4, _hoisted_5, _createVNode(_component_InnerComponent)]),
_createVNode(
_component_second_component,
{
"stat-fat": {label: "3333"},
test: "333",
},
null,
8 /* PROPS */,
["stat-fat"]
),
])
);
}
}
而创建vnode
的render()
的调用时机如下面首次渲染整体流程图所示,在一系列复杂流程的处理后,会触发renderComponentRoot()
中调用render()
函数(从而进行createBaseVNode
/createVNode
)获取组件内部的vnode
数据,然后进行patch()
首次渲染整体流程图
首次渲染整体流程-源码概要分析
const createApp = ((...args) => {
const { createApp } = ensureRenderer(); // 第1部分
const app = createApp(...args); // 第2部分
//...
const { mount } = app;
app.mount = (containerOrSelector) => { // 第3部分
//...
const proxy = mount(container, false, container instanceof SVGElement);//第4部分
//...
return proxy;
};
return app;
});
第1部分: ensureRenderer() 创建渲染器,获取createApp
由下面的代码块可以知道,会将render()
函数传入createAppAPI(render)
进行createApp()
方法的创建
function ensureRenderer() {
return (renderer || (renderer = createRenderer(rendererOptions)));
}
function createRenderer(options) {
return baseCreateRenderer(options);
}
function baseCreateRenderer(options, createHydrationFns) {
//...初始化很多方法
const render = (vnode, container, isSVG) => { };
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
第2部分: 使用createApp()创建app对象
从下面的代码块可以知道,本质是将baseCreateRenderer()
产生的render函数
传入作为app.mount()
中执行的一环,然后返回创建的app对象
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext();
// ...
let isMounted = false;
const app = (context.app = {
_uid: uid++,
_component: rootComponent,
_props: rootProps,
_context: context,
// ...省略
mount(rootContainer, isHydrate, isSVG) {
// ...省略
vnode.appContext = context;
render(vnode, rootContainer, isSVG);
// ...省略
}
//...省略多种方法
});
return app;
}
}
其中createAppContext()
返回的一个初始化的对象,从下面的代码块可以看到全局变量app.config.globalProperties
export function createAppContext(): AppContext {
return {
app: null as any,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {}
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
}
}
第3部分: 重写app.mount,暴露出来该方法,在外部显示调用
由于createApp重名两次,为了避免认错,将初始化示例代码再写一次
根据初始化示例代码
,在createApp()
中创建了app对象
,并且进行了app.mount()
方法的书写,从下面示例代码可以知道,最终会调用app.mount("#app")
import App from "./App.vue";
const app = createApp(App);
app.mount("#app");
const createApp = ((...args) => {
const { createApp } = ensureRenderer(); // 第1部分
const app = createApp(...args); // 第2部分
//...
const { mount } = app;
app.mount = (containerOrSelector) => { // 第3部分
//...
const proxy = mount(container, false, container instanceof SVGElement);//第4部分
//...
return proxy;
};
return app;
});
重写app.mount("#app")详细源码分析
containerOrSelector
可以为字符串
/DOM
,使用normalizeContainer()
进行整理- 判断如果没有
render函数
和template内容
,则使用innerHTML
作为组件模板内容 - 挂载前清空
innerHTML
内容 - 执行原始的
mount()
方法,返回执行后的结果
app.mount = (containerOrSelector) => { // 第3部分
const container = normalizeContainer(containerOrSelector);
if (!container)
return;
const component = app._component;
if (!isFunction(component) && !component.render && !component.template) {
//在 DOM 模板中可能会执行 JS 表达式
component.template = container.innerHTML;
}
container.innerHTML = '';
const proxy = mount(container, false, container instanceof SVGElement);//第4部分
//...
return proxy;
};
function normalizeContainer(container) {
if (isString(container)) {
const res = document.querySelector(container);
return res;
}
return container;
}
第4部分: 触发原始的app.mount(dom)执行(重写app.mount执行过程中)
整体源码
// 重写app.mount: const createApp = ((...args) => {})里面逻辑
const { mount } = app;
app.mount = (containerOrSelector) => { // 第3部分
const proxy = mount(container, false, container instanceof SVGElement);//第4部分
return proxy;
};
// 第4部分: 原始的app.mount:
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext();
const app = (context.app = {
mount(rootContainer, isHydrate, isSVG) {
if (!isMounted) {
const vnode = createVNode(rootComponent, rootProps);
vnode.appContext = context;
render(vnode, rootContainer, isSVG);
isMounted = true;
app._container = rootContainer;
rootContainer.__vue_app__ = app;
return getExposeProxy(vnode.component) || vnode.component.proxy;
}
}
});
return app
}
}
原始的app.mount的核心代码:render()
(非<template>
转化后的render
)
从上面整体代码可以知道,主要还是触发了render()
方法,这里的render()
方法是createApp()
时创建的app
时创建的内部方法!!!!跟一开始创建vnode
的render()
方法是不同的方法!!!!!
- 如果要渲染的vnode节点为空,则触发销毁逻辑
- 如果要渲染的vnode节点不为空,则进行创建/更新逻辑:
patch()
- 然后触发
Post
优先级的队列执行 - 最后将
vnode
赋值给container._vnode
render(vnode, rootContainer, isSVG);
// function baseCreateRenderer(options, createHydrationFns) {}
function baseCreateRenderer(options, createHydrationFns) {
const render = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
}
else {
// 也是定义在baseCreateRenderer()中
patch(container._vnode || null, vnode, container, null, null, null, isSVG);
}
flushPostFlushCbs();
container._vnode = vnode;
};
}
原始的app.mount的返回值:getExposeProxy(vnode.component)
处理instance.exposed
的一些情况,进行ref解码
/处理key=$的情况,进行代理返回
function getExposeProxy(instance) {
// ...改写
if (!instance.exposed) {
return;
}
if (instance.exposeProxy) {
return instance.exposeProxy;
} else {
instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
get(target, key) {
if (key in target) {
return target[key];
}
else if (key in publicPropertiesMap) {
return publicPropertiesMap[key](instance);
}
}
});
}
}
function markRaw(value) {
def(value, "__v_skip" /* SKIP */, true);
return value;
}
function proxyRefs(objectWithRefs) {
// shallowUnwrapHandlers进行Proxy的拦截,然后进行ref的解码,没有其它逻辑
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
const publicPropertiesMap = extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (shallowReadonly(i.props)),
$attrs: i => (shallowReadonly(i.attrs)),
$slots: i => (shallowReadonly(i.slots)),
$refs: i => (shallowReadonly(i.refs)),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (resolveMergedOptions(i)),
$forceUpdate: i => () => queueJob(i.update),
$nextTick: i => nextTick.bind(i.proxy),
$watch: i => (instanceWatch.bind(i))
});
首次渲染-App.vue组件的patch()
流程分析
初始化createApp
后的app.mount("#el")
中的render()
会触发patch()
方法,进行vnode的渲染
,当前也有很多地方也会触发patch()
方法,该方法主要执行的流程为:
- 如果新旧节点相同,则直接返回
- 如果新旧节点不是同种类型,则直接销毁旧节点,并且将
n1
置为null
,保证后面执行逻辑正确 - 根据
新vnode
节点的类型,进行不同方法的调用,如文本调用processText()
,如组件调用processComponent()
const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = isHmrUpdating ? false : !!n2.dynamicChildren) => {
if (n1 === n2) {
return;
}
// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1);
unmount(n1, parentComponent, parentSuspense, true);
n1 = null;
}
if (n2.patchFlag === -2 /* BAIL */) {
optimized = false;
n2.dynamicChildren = null;
}
const { type, ref, shapeFlag } = n2;
switch (type) {
case Text: // 处理文本
processText(n1, n2, container, anchor);
break;
case Comment: // 处理注释
processCommentNode(n1, n2, container, anchor);
break;
case Static: // 处理静态节点
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG);
}
else {
patchStaticNode(n1, n2, container, isSVG);
}
break;
case Fragment: // 处理Frgment元素
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
break;
default:
if (shapeFlag & 1 /* ELEMENT */) {//处理普通DOM元素
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else if (shapeFlag & 6 /* COMPONENT */) {//处理组件元素
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
else if (shapeFlag & 64 /* TELEPORT */) {//处理<teleport>
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);
}
else if (shapeFlag & 128 /* SUSPENSE */) {//处理suspense
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);
}
else {
warn$1('Invalid VNode type:', type, `(${typeof type})`);
}
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2);
}
};
初始化触发组件处理processComponent()
由于是初始化createApp
,因此此时n1=null
,触发mountComponent()
逻辑
updateComponent()
是数据变化后的组件更新逻辑,后面再分析
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
n2.slotScopeIds = slotScopeIds;
if (n1 == null) {
if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
} else {
mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
} else {
updateComponent(n1, n2, optimized);
}
};
mountComponent()
核心代码概述
mountComponent
函数流程主要为:
- 创建组件实例
- 设置组件实例
- 设置并运行带有
effect
的渲染函数
const mountComponent = (...args) => {
// 创建组件实例
initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense)
const instance = initialVNode.component;
//设置组件实例
setupComponent(instance);
//设置并运行带有effect的渲染函数
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
}
首次渲染-mountComponent()
核心代码详细分析
createComponentInstance
创建组件实例
初始化组件实例,逻辑非常简单,只是构建一个ComponentInternalInstance
对象,包括非常多的属性
function createComponentInstance(vnode, parent, suspense) {
const type = vnode.type;
//...省略
const instance: ComponentInternalInstance = {
//...省略非常多的配置参数
vnode, // Vnode representing this component in its parent's vdom tree
type,
parent, // ComponentInternalInstance
root // ComponentInternalInstance
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
//...省略
return instance
}
setupComponent
设置组件实例
整体概述
- 判断是否是有状态组件:
const isStateful = isStatefulComponent(instance)
- 初始化
props
,构建props和attrs属性 - 初始化
slots
- 执行
setupStatefulComponent()
function setupComponent(instance, isSSR = false) {
const { props, children } = instance.vnode;
// isStatefulComponent = instance.vnode.shapeFlag & 4
const isStateful = isStatefulComponent(instance);
initProps(instance, props, isStateful, isSSR);
initSlots(instance, children);
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined;
return setupResult;
}
核心逻辑setupStatefulComponent
整体概述
初始化props
和slots
后调用setupStatefulComponent()
,主要执行流程:
- 创建渲染代理属性访问缓存
- 创建渲染上下文代理,并且给它
Object.defineProperty
一个__v_skip
属性,__v_skip
可以阻止响应式追踪,监测到__v_skip
立刻返回target
处理
setup()
函数- 如果存在
setup()
,则执行setup
函数,获取执行结果setupResult
,根据返回结果的类型,返回方法(渲染函数)/返回对象(响应式数据/方法),构建instance.render方法
/instance.setupState对象
,然后再调用finishComponentSetup()
方法处理 - 如果不存在
setup()
,直接调用finishComponentSetup()
方法处理 finishComponentSetup()
方法是为了合并Options API
和Composition API
的生命周期以及各种inject
、methods
、data
、computed
等属性
- 如果存在
function setupStatefulComponent(instance, isSSR) {
const Component = instance.type;
//...省略一些简单的校验逻辑
// 0. 创建渲染代理属性访问缓存
instance.accessCache = Object.create(null);
// 1. 创建public实例 / 渲染代理,并且给它Object.defineProperty一个__v_skip属性
// def(value, "__v_skip" /* SKIP */, true);
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));
const { setup } = Component;
if (setup) {
// 2. 处理setup()函数
// ...进行一系列处理
}
else {
// 3. 没有setup函数时,直接调用finishComponentSetup
finishComponentSetup(instance, isSSR);
}
}
第1步:instance.proxy创建渲染上下文
const PublicInstanceProxyHandlers = {
get({ _: instance }, key) {},
set({ _: instance }, key, value) {},
has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {}
};
由上面整体概述的代码可以知道,主要是将instance.ctx
进行了new Proxy()
的拦截,由于在new Proxy()
后进行了__v_skip
的赋值,因此这里只是单纯拦截了key=get
、key=set
和key=has
方法,没有做任何的响应式处理,也阻止任何的响应式处理
get方法拦截(instance.proxy)
从instance
中获取多种数据,包括ctx
、setupState
、data
、props
、accessCache
、type
、appContext
ctx
:用户自定义的数据,包含了所有可以在<template>
中访问的数据,如果是Ref
/Rective
数据,拿到的是toRaw
的数据- 包含
Options API
中的所有this.xxx
数据,比如data
、methods
产生的this.xxx
/this.xxx()
- 包含
Composition API
中setup(){return {xxx}}
进行return
的变量(原始Raw数据
)
- 包含
setupState
:拿到在setup()
中的数据,比如具备响应式Ref
数据data
:Options API
的data
props
:Options API
的props
accessCache
:访问过的数据的缓存,对渲染上下文的每个属性访问都会调用此getter,这其中最昂贵的部分,是多个hasOwn()
调用。访问普通对象,使用accessCache(with null prototype)
缓存对象要快得多,每次访问过数据,就进行accessCode[key]=xxx
的缓存type
:vnode
类型,有Fragment
、Text
、Comment
、Static
以及组件类型等等,可以获取到CSS Module
数据appContext
:createApp
的app
上下文,具有appContext.config.globalProperties
等全局方法
get({ _: instance }, key) {
const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
}
在accessCache
中查找是否有符合条件的key
,如果有,则拿出对应的数据类型,然后直接从对应的数据类型中获取,省去hasOwn()
比较
if (key[0] !== '$') {
const n = accessCache[key];
if (n !== undefined) {
switch (n) {
case 1 /* SETUP */:
return setupState[key];
case 2 /* DATA */:
return data[key];
case 4 /* CONTEXT */:
return ctx[key];
case 3 /* PROPS */:
return props[key];
// default: just fallthrough
}
}
}
如果没有accessCache
,则按照顺序:setupState
>data
>props
>ctx
查找是否有符合条件的key
,并且跟新accessCache
if (key[0] !== '$') {
const n = accessCache[key];
if (n !== undefined) {
// ...accessCode[key]存储对应的类型
} else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
accessCache[key] = 1 /* SETUP */;
return setupState[key];
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache[key] = 2 /* DATA */;
return data[key];
} else if (
(normalizedProps = instance.propsOptions[0]) &&
hasOwn(normalizedProps, key)) {
accessCache[key] = 3 /* PROPS */;
return props[key];
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache[key] = 4 /* CONTEXT */;
return ctx[key];
}
else if (shouldCacheAccess) {
accessCache[key] = 0 /* OTHER */;
}
}
如果setupState
>data
>props
>ctx
都找不到对应的key
,则accessCache[key] = 0 /* OTHER */;
此时key
有两种情况,一种是key
是以$
作为开头的字符串,一种是确实不存在于setupState
>data
>props
>ctx
中
这个时候会在publicPropertiesMap
中寻找是否有符合条件的key
// const publicPropertiesMap = extend(Object.create(null), {
// $: i => i,
// $el: i => i.vnode.el,
// $data: i => i.data,
// $props: i => (shallowReadonly(i.props)),
// $attrs: i => (shallowReadonly(i.attrs)),
// $slots: i => (shallowReadonly(i.slots)),
// $refs: i => (shallowReadonly(i.refs)),
// $parent: i => getPublicInstance(i.parent),
// $root: i => getPublicInstance(i.root),
// $emit: i => i.emit,
// $options: i => (resolveMergedOptions(i)),
// $forceUpdate: i => () => queueJob(i.update),
// $nextTick: i => nextTick.bind(i.proxy),
// $watch: i => (instanceWatch.bind(i))
// });
const publicGetter = publicPropertiesMap[key];
if (publicGetter) {
if (key === '$attrs') {
track(instance, "get" /* GET */, key);
markAttrsAccessed();
}
return publicGetter(instance);
}
如果publicPropertiesMap
中没有符合条件的key
,会继续向下寻找CSS Module
const publicGetter = publicPropertiesMap[key];
if (publicGetter) {
// ...
}
else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) {
return cssModule;
}
如果CSS Module
没有符合条件的key
,会继续向下寻找ctx
用户自定义的数据(上面的ctx
寻找是针对非$开头的key
,这里包括了$开头的key
)
const publicGetter = publicPropertiesMap[key];
if (publicGetter) {
// ...
} else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) {
//...
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
// user may set custom properties to `this` that start with `$`
accessCache[key] = 4 /* CONTEXT */;
return ctx[key];
}
如果ctx
没有符合条件的key
,继续往全局属性中查找
const publicGetter = publicPropertiesMap[key];
if (publicGetter) {
// ...
} else if ((cssModule = type.__cssModules) && (cssModule = cssModule[key])) {
//...
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
// ...
} else if (((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key)) {
return globalProperties[key];
}
set方法拦截(instance.proxy)
跟get
方法一样,按照setupState
>data
>props
,判断是否存在该key
,然后进行值的更新
如果setupState
>data
>props
,则直接进行ctx[key] = value
的赋值
ctx[key]=value
的赋值类似于在mounted(){this.test=222}=>那么会存储ctx[test]=222,这样的数据只能共享于组件自定义数据ctx
props是readonly 数据,无法set更改,直接阻止
Vue内部以$开头的属性也是不可以更改,直接阻止
set({ _: instance }, key, value) {
const { data, setupState, ctx } = instance;
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value;
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value;
} else if (hasOwn(instance.props, key)) {
return false;
}
if (key[0] === '$' && key.slice(1) in instance) {
return false;
} else {
ctx[key] = value;
}
return true;
}
has方法拦截(instance.proxy)
按照accessCache
>data
>setupState
>props
>ctx
>publicPropertiesMap
>globalProperties
进行hasOwn
的判断
has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {
let normalizedProps;
return (!!accessCache[key] ||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
hasOwn(ctx, key) ||
hasOwn(publicPropertiesMap, key) ||
hasOwn(appContext.config.globalProperties, key));
}
第2步:setup存在时的处理
整体概述
const { setup } = Component;
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null);
setCurrentInstance(instance);
pauseTracking();
const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [shallowReadonly(instance.props), setupContext]);
resetTracking();
unsetCurrentInstance();
if (isPromise(setupResult)) {
setupResult.then(unsetCurrentInstance, unsetCurrentInstance);
instance.asyncDep = setupResult;
} else {
handleSetupResult(instance, setupResult, isSSR);
}
} else {
//...
}
createSetupContext创建setup上下文
只有当setup.length>1
,才会触发createSetupContext(instance)
,那是因为setupContext
代表着setup
的第二个参数,如下所示,setupContext=context,即setupContext={attrs, slots, emit, expose}
const App = {
setup(props, context) {
// context就是setupContext
// 此时setup.length=2
}
}
const App1 = {
setup(props) {
// context就是setupContext
// 此时setup.length=1
}
}
从下面createSetupContext
代码块可以知道,本质还是拿instance.xxx
的相关属性进行{attrs, slots, emit, expose}
的拼凑
function createSetupContext(instance) {
const expose = exposed => {
instance.exposed = exposed || {};
};
let attrs;
return {
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
},
slots: instance.slots,
emit: instance.emit,
expose
}
}
执行setup函数,获取执行结果setupResult
- 设置当前活跃的
instance
为当前的实例 shouldTrack
=false
,阻止响应式的依赖收集callWithErrorHandling
:使用统一的try{}catch{}
执行setup()
函数,并且传递shallowReadonly(instance.props)
和上面初始化的setupContext={attrs, slots, emit, expose}
- 回滚
shouldTrack
的状态 - 回滚
currentInstance
的状态,置为null
// currentInstance = instance 设置当前活跃的instance
setCurrentInstance(instance);
// shouldTrack=false,阻止响应式的依赖收集
pauseTracking();
// res = args ? fn(...args) : fn() 执行setup()函数,传递对应的props和context
const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [shallowReadonly(instance.props), setupContext]);
// 恢复shouldTrack状态
resetTracking();
// 将当前活跃的instance置为null
unsetCurrentInstance();
处理setupResult-handleSetupResult()
从下面的代码块可以知道,最终调用的是handleSetupResult()
方法
if (isPromise(setupResult)) {
// setup返回的是一个Promise的情况,较为少见,暂时不分析这种情况
setupResult.then(unsetCurrentInstance, unsetCurrentInstance);
instance.asyncDep = setupResult;
} else {
handleSetupResult(instance, setupResult, isSSR);
}
function handleSetupResult(instance, setupResult, isSSR) {
if (isFunction(setupResult)) {
instance.render = setupResult;
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult);
}
finishComponentSetup(instance, isSSR);
}
从上面代码可以知道,setup()
执行完毕后可能返回一个function()
,如下所示,setup 返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态,handleSetupResult()
中直接将返回的渲染函数赋值给instance.render
返回一个渲染函数将会阻止我们返回其他东西,我们想通过模板引用将这个组件的方法暴露给父组件可以通过调用 expose() 解决这个问题
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
return {
count,
increment
}
}
}
setup()
可以直接返回一个object
(如上面代码块所示),handleSetupResult()
将返回的对象进行proxyRefs
的封装,如下面代码块所示,本质就是进行new Proxy()
的拦截,然后进行ref类型的判断
以及对应的解构,除了解构之外,其它操作就是普通的get
和set
方法
由于proxyRefs会自动解构,因为我们setup()返回的Ref类型
数据,也不用手动写ref.value
,直接写ref
即可,会自动解构
function proxyRefs(objectWithRefs) {
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
const shallowUnwrapHandlers = {
// unref = isRef(ref) ? ref.value : ref;
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
const oldValue = target[key];
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
else {
return Reflect.set(target, key, value, receiver);
}
}
};
处理setupResult-finishComponentSetup()
根据setupResult
数据的类型做出处理后,最终还是调用了finishComponentSetup()
方法,与setup不存在时的处理
调用的方法一致,因此这个阶段的分析直接放在下面一小节中
第3步:setup不存在时的处理-finishComponentSetup()
整体概述
function finishComponentSetup(instance, isSSR, skipOptions) {
const Component = instance.type;
if (!instance.render) {
if (!isSSR && compile && !Component.render) {
const template = Component.template;
startMeasure(instance, `compile`);
//...拼接finalCompilerOptions
Component.render = compile(template, finalCompilerOptions);
endMeasure(instance, `compile`);
}
instance.render = (Component.render || NOOP);
if (installWithProxy) {
//for runtime-compiled render functions using `with` blocks
installWithProxy(instance);
}
}
// support for 2.x options
setCurrentInstance(instance);
pauseTracking();
applyOptions(instance);
resetTracking();
unsetCurrentInstance();
}
instance.render不存在,但是存在Component.template
一般开发.vue
程序时,会在运行编译阶段通过vue-loader
将组件的<template></template>
转化为render()
函数,以及转化对应的JavaScript
和CSS
,形成标准的渲染函数,因此我们一般都使用Runtime-only
版本的Vue.js
代码
但是存在一种情况,我们需要通过某种途径获取模版内容(比如从网络请求获取对应的模板数据),然后借助Vue.js
的Runtime+Compiler
版本,动态转化模板为render()
函数,如下面代码块所示,当instance.render=false
并且Component.template
存在时,动态转化形成instance.render
installWithProxy还不清楚是哪种情况的处理,先暂时搁置,后面遇到这种情况再回来补充完整细节
if (!instance.render) {
if (!isSSR && compile && !Component.render) {
const template = Component.template;
startMeasure(instance, `compile`);
//...拼接finalCompilerOptions
Component.render = compile(template, finalCompilerOptions);
endMeasure(instance, `compile`);
}
instance.render = (Component.render || NOOP);
if (installWithProxy) {
//for runtime-compiled render functions using `with` blocks
installWithProxy(instance);
}
}
function registerRuntimeCompiler(_compile) {
compile = _compile;
installWithProxy = i => {
if (i.render._rc) {
i.withProxy = new Proxy(i.ctx, RuntimeCompiledPublicInstanceProxyHandlers);
}
};
}
兼容Vue2的Options API模式-applyOptions ()
- 合并
Options API
和Composition API
的相关属性,包括inject
、methods
、data
、computed
、watch
、provide
- 合并
Options API
和Composition API
的生命周期,包括onBeforeMount
、onMounted
等等很多生命周期 - 处理
expose
、render
、inheritAttrs
、components
、directives
几个注意的点:
- 合并 Options API的data时使用了reactive(data)
beforeCreate生命周期
触发后才进行Options API
和Composition API
的合并- 合并完成
inject
、methods
、data
、computed
、watch
、provide
后触发created生命周期
- 然后再处理其它生命周期以及
expose
、render
、inheritAttrs
、components
、directives
function applyOptions(instance) {
const options = resolveMergedOptions(instance);
const publicThis = instance.proxy;
const ctx = instance.ctx;
// beforeCreate
if (options.beforeCreate) {
callHook(options.beforeCreate, instance, "bc" /* BEFORE_CREATE */);
}
const {
//...很多属性
} = options;
if (dataOptions) {
const data = dataOptions.call(publicThis, publicThis);
instance.data = reactive(data);
}
// created
if (created) {
callHook(created, instance, "c" /* CREATED */);
}
registerLifecycleHook(onBeforeMount, beforeMount);
//.......
}
setupRenderEffect
设置并运行带有effect的渲染函数
const mountComponent = (...args) => {
// 创建组件实例
initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense)
const instance = initialVNode.component;
//设置组件实例
setupComponent(instance);
//设置并运行带有effect的渲染函数
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
}
上面已经详细分析了createComponentInstance()
和setupComponent()
的执行逻辑,这一节将讲解mountComponent
的最后一个逻辑:setupRenderEffect()
setupRenderEffect整体概述
从下面的代码块可以知道,最终触发了update()
,也就是触发effect.run.bind(effect)
,由后续的响应式系统文章可以知道
最终执行的顺序为:queueJob(instance.update)
->effect.run.bind(effect)
->getter: componentUpdateFn()
,此时instance.isMounted
=false
,进行renderComponentRoot和patch
const setupRenderEffect = (instance, ...args) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
//...进行renderComponentRoot和patch
}
}
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
));
const update = (instance.update = effect.run.bind(effect));
update.id = instance.uid;
toggleRecurse(instance, true);
update()
}
componentUpdateFn
componentUpdateFn
剔除生命周期等执行代码以及更新组件
的逻辑代码后,最终的核心代码可以精简到下面的代码块
const componentUpdateFn = () => {
if (!instance.isMounted) {
toggleRecurse(instance, false);
// beforeMount hook处理
toggleRecurse(instance, true);
const subTree = (instance.subTree = renderComponentRoot(instance));
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
initialVNode.el = subTree.el;
instance.isMounted = true;
initialVNode = container = anchor = null;
}
}
renderComponentRoot(instance)
生成subTree
从下面的代码块可以知道,最终调用的是render.call()
,这里的render()
是<template>
转化后的render函数
上面createApp()
流程执行app.mount()
所触发的render()
是ensureRenderer()
创建的自定义渲染器内部建立的render()
函数,不是<template>
转化后的render函数
function renderComponentRoot(instance) {
const {
render
//...
} = instance
const prev = setCurrentRenderingInstance(instance);
// ...省略代码
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// 有状态组件
const proxyToUse = withProxy || proxy;
result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
} else {
// functional组件
}
// ...省略代码
setCurrentRenderingInstance(prev);
return result;
}
通过render()函数的createVNode()
等一些方法创建vnode
数据后,进行normalizeVNode()
的简单整理
function normalizeVNode(child) {
// child就是render生成后的vnode数据
if (child == null || typeof child === 'boolean') {
// empty placeholder
return createVNode(Comment);
} else if (isArray(child)) {
// fragment
return createVNode(Fragment, null,
// #3666, avoid reference pollution when reusing vnode
child.slice());
} else if (typeof child === 'object') {
// already vnode, this should be the most common since compiled templates
// always produce all-vnode children arrays
return cloneIfMounted(child);
} else {
// strings and numbers
return createVNode(Text, null, String(child));
}
}
function cloneIfMounted(child) {
return child.el === null || child.memo ? child : cloneVNode(child);
}
整理之后,result
是组件最顶部元素的vnode数据,比如下面的代码块,顶部有两个div元素
,因此会默认生成一个最外层的fragment元素
<template>
<div></div>
<div></div>
</template>
subTree = {
type: "Symbol(Fragment)"
children: [
{
type: "div",
props: { id: 'item1' },
el: "div#item1"// 实际上是DOM元素
},
{
type: "div",
props: { id: 'item1' },
el: "div#item2" // 实际上是DOM元素
}
]
}
patch(null, subTree, container)
如整体概述的代码所示,当执行renderComponentRoot()
创建vnode数据
后,此时vnode数据
是以顶部元素的数据,然后再次触发patch()
方法,此时跟ensureRenderer().createApp().mount()
触发的patch()
方法有两个点不一样,一个是vnode数据
,此时的vnode数据
类型是Symbol(Fragment)
,而不是Component数据类型
,因此不会触发processComponent
,会触发processFragment()
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
const patch = (n1, n2, container, ...args) => {
//...
const { type, ref, shapeFlag } = n2;
switch (type) {
case Text: // 处理文本
processText(n1, n2, container, anchor);
break;
case Fragment: // 处理Frgment元素
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
break;
default:
if (shapeFlag & 1 /* ELEMENT */) {//处理普通DOM元素
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
} else if (shapeFlag & 6 /* COMPONENT */) {//处理组件元素
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
}
//...
}
processFragment()
从下面的代码块可以知道,最终processFragment()
触发的是mountChildren()
,最终也是触发每一个children vnode
的patch()
const processFragment = (n1, n2, container, anchor, ...xx) => {
const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''));
const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''));
if (n1 == null) {
hostInsert(fragmentStartAnchor, container, anchor);
hostInsert(fragmentEndAnchor, container, anchor);
mountChildren(n2.children, container, fragmentEndAnchor, ...xxx);
}
};
const mountChildren = (children, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, start = 0) => {
for (let i = start; i < children.length; i++) {
const child = (children[i] = optimized
? cloneIfMounted(children[i])
: normalizeVNode(children[i]));
patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
};
借助下面的示例,我们可以知道,children vnode
是type=div
的vnode数据类型
,因此patch()
会触发processElement()
代码
<template>
<div></div>
<div></div>
</template>
subTree = {
type: "Symbol(Fragment)"
children: [
{
type: "div",
props: { id: 'item1' },
el: "div#item1"// 实际上是DOM元素
},
{
type: "div",
props: { id: 'item1' },
el: "div#item2" // 实际上是DOM元素
}
]
}
processElement()
从下面的代码块可以知道,processElement()
最终触发的是mountElement()
const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
isSVG = isSVG || n2.type === 'svg';
if (n1 == null) {
mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
};
从下面的代码块可以知道,最终mountElement()
触发核心流程是:
- 使用
hostCreateElement
创建一个DOM 元素
,比如创建一个div DOM元素
- 根据类型,如果为
<div>textContent</div>
,即children是文本
的时候,直接调用hostSetElementText
,即setText
- 根据类型,如果为
<div><div></div></div>
,即children还是其它DOM元素
的时候,需要再调用mountChildren()
->patch(children)
,然后根据type
,再调用不同的处理函数,比如<div><div></div><div></div></div>
=mountChildren()
->processElement(children1)
->processElement(children2)
- 最终调用
hostInsert
进行元素的插入,比如App.vue
,传递的container
就是app.mount("#el")
中的div#el
,最终会将App.vue
的最最外层创建的元素插入到div#el
的子元素的末尾
insert:先子节点进行hostInsert -> 后父节点进行hostInsert -> 最终挂载到最外层的DOM上
const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
let el;
const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode;
// ...
el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is, props);
// mount children first, since some props may rely on child content
// being already rendered, e.g. `<select value>`
if (shapeFlag & 8 /* TEXT_CHILDREN */) {
hostSetElementText(el, vnode.children);
} else if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', slotScopeIds, optimized);
}
// ...
hostInsert(el, container, anchor);
// ...
};
组件更新-整体流程-触发父组件和子组件更新
示例
<script>
const SecondComponent = {
template: '<button>SecondComponent: {{count}}</button>',
props: {
count: Number
}
}
const InnerComponent = {
template: '<span>InnerComponent: {{proxyCount}}</span><div>这是InnerComponent的第2个元素</div>',
props: {
count: Number
}
}
const InnerComponent1 = {
template: '<span>InnerComponent1: {{proxyCount}}</span>',
props: {
count: Number
}
}
</script>
<script type="text/x-template" id="app">
<div id="app-wrapper">
<div id="app-content1">
<div>app-content1: app-content1{{proxyCount}}</div>
<InnerComponent v-if="proxyCount>=1" :count="proxyCount"></InnerComponent>
<InnerComponent1 v-else :count="proxyCount"></InnerComponent1>
</div>
<second-component :count="proxyCount"></second-component>
</div >
</script >
onMounted(() => {
setTimeout(() => {
console.clear();
proxyCount.value = 8;
}, 500);
console.error("onMounted():" + proxyCount.value);
});
流程图
整体流程图源码分析
前置分析
渲染Effect的依赖收集
由上面首次渲染分析可以知道,我们在app.mount("#app")
->内部render()
->patch()
->mountComponent()
->setupRenderEffect()
->为Component
建立ReactiveEffect
->手动调用建立ReactiveEffect
时传入的getter
:componentUpdateFn
->触发vue-loader
转化的render()
->触发响应式数据Proxy
的getter
,从而触发依赖收集
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 首次渲染逻辑
// 执行<template>转化的render()函数,触发响应式数据的getter,进行依赖收集
const subTree = (instance.subTree = renderComponentRoot(instance));
// 进行vnode数据的挂载
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
} else {
}
};
const effect = (instance.effect = new ReactiveEffect(componentUpdateFn,
() => queueJob(instance.update), instance.scope));
const update = (instance.update = effect.run.bind(effect));
update.id = instance.uid;
toggleRecurse(instance, true);
update();
渲染Effect的派发更新
当渲染Effect
的响应式数据发生变化时,触发渲染Effect
的重新执行,即触发componentUpdateFn()
重新执行,此时instance.isMounted=true
const componentUpdateFn = () => {
if (!instance.isMounted) {
} else {
// 更新组件的更新逻辑
}
};
App.vue触发派发更新详细分析
主要触发的流程为
- 此时
next
为空,因此不会执行updateComponentPreRender()
renderComponentRoot()
: 执行vue-loader
转化<template>
后的render()
函数,渲染新的组件根部vnode
数据(携带children)patch(prevTree, nextTree)
:进行新旧数据的具体比对更新逻辑next.el=nextTree.el
:缓存更新后的el
结合示例代码和上述流程,我们可以知道,App.vue
会触发componentUpdateFn()
,从而执行流程
next = vnode
const nextTree = renderComponentRoot(instance)
patch(prevTree, nextTree)
此时传入的nextTree
是组件App.vue
根部节点type=div#app-wrapper
,children=[{type:"div#app-content1"}, {type:"div#app-content2"}]
的vnode
数据
const componentUpdateFn = () => {
if (!instance.isMounted) {
} else {
// 更新组件的更新逻辑
let {next, bu, u, parent, vnode} = instance;
toggleRecurse(instance, false);
if (next) {
next.el = vnode.el;
updateComponentPreRender(instance, next, optimized);
} else {
next = vnode;
}
toggleRecurse(instance, true);
const nextTree = renderComponentRoot(instance);
const prevTree = instance.subTree;
instance.subTree = nextTree;
patch(prevTree, nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el),
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree), instance, parentSuspense, isSVG);
next.el = nextTree.el;
}
};
核心流程patch()
分析
- 当两个
vnode
的n1.type === n2.type && n1.key === n2.key
不成立时,isSameVNodeType=false
,直接调用n1.umount()
,然后设置n1=null
,跟首次渲染的流程一致 - 当两个
vnode
的类型相同,会触发更新逻辑,结合示例分析,由于nextTree
是type=div
,因此会触发patch()
->processElement()
的处理
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
const patch = (n1, n2, container, ...args) => {
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1);
unmount(n1, parentComponent, parentSuspense, true);
n1 = null;
}
const { type, ref, shapeFlag } = n2;
switch (type) {
case Text: // 处理文本
processText(n1, n2, container, anchor);
break;
case Fragment: // 处理Frgment元素
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
break;
default:
if (shapeFlag & 1 /* ELEMENT */) {//处理普通DOM元素
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
} else if (shapeFlag & 6 /* COMPONENT */) {//处理组件元素
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
}
}
const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
isSVG = isSVG || n2.type === 'svg';
if (n1 == null) {
// ...首次渲染
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
};
processElement()->patchElement()
分析
由于patchFlag和Block涉及到的知识点较多,留在后续文章编译优化中讲解,这里示例调试代码,强制force full diff
patchElement()
的流程主要为:
patchChildren()
:进行vnode children
的对比更新处理patchProps()
: 更新当前vnode
的props
数据,包括style
、class
、id
、on事件
、DOM Prop(.stop等等)
等等可以挂载在普通HTML元素,比如<div>
/Vue组件元素,比如<App>
上面的属性
const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
// 暂时回避patchFlag,全部更新都是full diff
parentComponent && toggleRecurse(parentComponent, true);
// if (isHmrUpdating) {
if (true) {
// HMR updated, force full diff
patchFlag = 0;
optimized = false;
dynamicChildren = null;
}
// full diff
patchChildren(n1, n2, el, null, parentComponent, parentSuspense, areChildrenSVG, slotScopeIds, false);
// unoptimized, full diff
patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG);
};
patchChildren:更新children
暂时回避patchFlag相关逻辑,全部更新都是full diff
从下面代码块可以知道,patchChildren()
直接9种条件进行列举处理
const patchChildren = (n1, n2, container, ...args) => {
const {patchFlag, shapeFlag} = n2;
// children has 3 possibilities: text, array or no children.
if (shapeFlag & 8 /* TEXT_CHILDREN */) {
// text children fast path
if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
unmountChildren(c1, parentComponent, parentSuspense);
}
if (c2 !== c1) {
hostSetElementText(container, c2);
}
} else {
if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
// prev children was array
if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
// two arrays, cannot assume anything, do full diff
patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
} else {
// no new children, just unmount old
unmountChildren(c1, parentComponent, parentSuspense, true);
}
} else {
// prev children was text OR null
// new children is array OR null
if (prevShapeFlag & 8 /* TEXT_CHILDREN */) {
hostSetElementText(container, '');
}
// mount new if array
if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
mountChildren(c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
}
}
};
条件1:旧children
文本->新children
文本
hostSetElementText
:更新文本内容
条件2:旧children
数组->新children
文本
unmountChildren
:先移除所有的旧vnode
的children数组
hostSetElementText
:设置DOM
的内部元素为新vnode
的children
文本
条件3:旧children
空->新children
文本
hostSetElementText
:设置DOM
的内部元素为新vnode
的children
文本
条件4:旧children
文本->新children
数组
hostSetElementText
:清空文本内容mountChildren
:初始化数组vnode children
条件5:旧children
数组->新children
数组
patchKeyedChildren
:diff算法比较更新,尽量少增删更新数据 ,具体分析请看文章Vue3源码-diff算法-patchKeyChildren流程浅析
条件6:旧children
空->新children
数组
mountChildren
:直接初始化数组vnode children
条件7:旧children
文本->新children
空
hostSetElementText
:清空文本内容
条件8:旧children
数组->新children
空
unmountChildren
:先移除所有的旧vnode
的children数组
条件9:旧children
空->新children
空
- 什么都不做
patchProps:更新props、class等各种VNode数据
暂时回避patchFlag相关逻辑,全部更新都是full diff
- 获取
newProps
对应的key
,更新oldProps
对应key
的value
newProps不存在的key
,代表已经废弃,删除oldProps
已经废弃的key- 更新
key=value
的值
const patchProps = (el, vnode, oldProps, newProps, parentComponent, parentSuspense, isSVG) => {
if (oldProps !== newProps) {
for (const key in newProps) {
// empty string is not valid prop
if (isReservedProp(key))
continue;
const next = newProps[key];
const prev = oldProps[key];
// defer patching value
if (next !== prev && key !== 'value') {
hostPatchProp(el, key, prev, next, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren);
}
}
if (oldProps !== EMPTY_OBJ) {
for (const key in oldProps) {
if (!isReservedProp(key) && !(key in newProps)) {
hostPatchProp(el, key, oldProps[key], null, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren);
}
}
}
if ('value' in newProps) {
hostPatchProp(el, 'value', oldProps.value, newProps.value);
}
}
};
processComponent()->updateComponent()
分析
由示例可知,最终会触发子Component
的渲染更新,最终触发patch()
->processComponent()
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
n2.slotScopeIds = slotScopeIds;
if (n1 == null) {
// ...首次渲染
} else {
updateComponent(n1, n2, optimized);
}
};
const updateComponent = (n1, n2, optimized) => {
const instance = (n2.component = n1.component);
if (shouldUpdateComponent(n1, n2, optimized)) {
// ...省略异步相关if分支逻辑
instance.next = n2;
// 监测是否已经执行,防止重复更新
invalidateJob(instance.update);
// instance.update is the reactive effect.
instance.update();
} else {
// no update needed. just copy over properties
n2.component = n1.component;
n2.el = n1.el;
instance.vnode = n2;
}
};
上面代码块的流程为:
shouldUpdateComponent
:比对两个vnode的props
、children
、dirs
、transition
,从而返回是否需要更新的值- 如果不需要更新,则直接将覆盖属性即可
如果需要更新
instance.next = n2
- 监测当前组件的
渲染effect
是否已经在队列中,防止重复更新 - 触发
instance.update()
,由下面mountComponent()
相关源码分析可以知道,最终instance.update()
=effect.run.bind(effect)
->getter: componentUpdateFn()
const mountComponent = (...args) => {
//......
//设置并运行带有effect的渲染函数
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
}
const setupRenderEffect = (instance, ...args) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
//...进行renderComponentRoot和patch
} else {
// updateComponent()->触发更新逻辑
}
}
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(instance.update),
instance.scope // track it in component's effect scope
));
const update = (instance.update = effect.run.bind(effect));
update.id = instance.uid;
toggleRecurse(instance, true);
update()
}
componentUpdateFn
: 渲染effect
触发更新组件逻辑
从上面示例可以知道,我们最外层是App.vue
,内部又有其它Component
组件,比如InnerComponent
,如下面代码块所示
<App>
<InnerComponent></InnerComponent>
</App>
从上面App.vue触发派发更新详细分析-核心流程patch()分析
,我们可以知道,响应式数据会触发App.vue
渲染Effect
重新执行,由于没有从processComponent()->updateComponent()
进入,因此instance.next
为空,而子组件InnerComponent
是经过processComponent()->updateComponent()
触发的effect.run()
方法,因此instance.next
不为空
从下面的代码块可以知道,流程为:
updateComponentPreRender
:更新新旧instance
的各种数据,包括instance.vnode
、instance.props
、instance.slots
等const nextTree = renderComponentRoot(instance)
:渲染新vnode
的根部节点vnode数据
patch(prevTree, nextTree)
:传入新旧vnode
的根部节点vnode数据
进行patch()
此时传入的nextTree
是子Component
根部节点的vnode
数据,然后触发patch()
,然后根据type
进行processElement
/processComponent
的递归深度处理,最终处理完成所有的结构的更新
const componentUpdateFn = () => {
if (!instance.isMounted) {
} else {
// 更新组件的更新逻辑
let {next, bu, u, parent, vnode} = instance;
toggleRecurse(instance, false);
if (next) {
next.el = vnode.el;
updateComponentPreRender(instance, next, optimized);
} else {
next = vnode;
}
toggleRecurse(instance, true);
const nextTree = renderComponentRoot(instance);
const prevTree = instance.subTree;
instance.subTree = nextTree;
patch(prevTree, nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el),
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree), instance, parentSuspense, isSVG);
next.el = nextTree.el;
}
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。