Vue3源码-整体渲染流程浅析
本文基于
Vue 3.2.30版本源码进行分析
为了增加可读性,会对源码进行删减、调整顺序、改变部分分支条件的操作,文中所有源码均可视作为伪代码
由于ts版本代码携带参数过多,不利于展示,大部分伪代码会取编译后的js代码,而不是原来的ts代码
文章内容
本文仅针对有状态组件、同步逻辑进行分析,不分析异步相关逻辑和函数组件等内容,相关源码展示也会剔除异步相关逻辑和函数组件等内容
- 流程图展示Vue3首次渲染整体流程
- 以流程图为基础,进行首次渲染整体流程的源码分析
- 流程图展示Vue3组件更新整体流程
- 以流程图为基础,进行组件更新整体流程的源码分析
由于本文篇幅较长,按照流程图看源码分析效果更佳
创建vnode
render()函数
- 使用
createBaseVNode进行普通元素节点的vnode创建 - 使用
createVNode进行Component组件节点的vnode创建
<template></template>内容最终会被编译成为render()函数,render()函数中执行createBaseVNode(_createElementVNode)/createVNode(_createVNode)返回渲染结果,在下面流程分析中
- 在一开始的
app.mount会触发App.vue的createVNode()函数获取vnode数据,进行patch()- 在处理组件内部数据时,会触发
renderComponentRoot()中调用render()函数(从而进行createBaseVNode/createVNode)获取组件内部的vnode数据,然后进行patch()
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"]
),
])
);
}
}
首次渲染整体流程图

首次渲染整体流程-源码概要分析
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的dataprops:Options API的propsaccessCache:访问过的数据的缓存,对渲染上下文的每个属性访问都会调用此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 = vnodeconst 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;
}
};
转载自:https://juejin.cn/post/7179851550943084603