该文章已不在维护,请移步
createApp
挂载方法
vue3的挂载方式和vue2不同,vue3是这样的:
Vue.createApp(options).mount('#demo')
流程图
createApp
废话不多说,我们来直接看源代码:
function ensureRenderer() {
return renderer || (renderer = createRenderer(rendererOptions));
}
const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args); // 创建app实例
const { mount } = app; // 重写mount方法,可以独立到不同的平台实现
app.mount = (containerOrSelector) => {
const container = normalizeContainer(containerOrSelector);
if (!container)
return;
const component = app._component
// 不是函数组件 && 没有自定义render && 没有自定义template
if (!isFunction(component) && !component.render && !component.template) {
// 将容器的html赋值给模版
component.template = container.innerHTML
}
// 安装前清除内容
container.innerHTML = '';
const proxy = mount(container); // DOM容器处理,执行createAppAPI.mount
if (container instanceof Element) {
container.removeAttribute('v-cloak');
container.setAttribute('data-v-app', '');
}
return proxy;
};
return app;
});
createRenderer
patch
这里 createRenderer 是关键,我们继续看:
function createRenderer(options) {
return baseCreateRenderer(options);
}
function baseCreateRenderer(options, createHydrationFns) {
const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, forcePatchProp: hostForcePatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent } = options;
/**
patch: 补丁
是vnode对dom的一种抽象表达,是将vnode的信息映射到dom上;
patch方法,接受新旧VNode节点与对应的容器,节点的兄弟节点,父组件,是否压缩优化等等,
整个patch的过程其实就是对应的VNode深度优先遍历的过程;最终构造出一颗完整的树
*/
const patch = (
n1, // 旧vnode
n2, // 新vnode
container,
anchor = null, // 定位锚点dom,用于往锚点前插节点
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false // 是否启用diff优化
) => {
// 存在旧的VNode,并且新的VNode类型(key|type)与之不同,销毁对应的旧结点
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1);
//n1设置为null,保证后面走整个节点的mount逻辑
n1 = null;
}
if (n2.patchFlag === -2 /* BAIL */) {
optimized = false;
n2.dynamicChildren = null;
}
/**
patch内部有两种情况:
1. 挂载 n1 为null
2. 更新
*/
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: //处理Fragment元素</>
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
break;
default: //elemment 处理DOM元素
if (shapeFlag & 1 /* ELEMENT */) {
// 处理element
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
else if (shapeFlag & 6 /* COMPONENT */) {
// 处理组件
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
}
else if (shapeFlag & 64 /* TELEPORT */) {
// 处理teleport
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
else if ( shapeFlag & 128 /* SUSPENSE */) {
// 处理 suspense
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized, internals);
}
else {
warn('Invalid VNode type:', type, `(${typeof type})`);
}
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2);
}
}
const processText = () => {...}; // 处理文本
const processCommentNode = () => {...} //处理注释
const mountStaticNode = () => {...} // 挂载静态节点
const patchStaticNode = () => {...} // 更新静态节点
const processElement = () => {...} // 处理element
const mountElement = () => {...} // 挂载element
const mountChildren = () => {...} // 挂载子元素,跟深度遍历上面的patch方法
const patchElement = () => {...} // 更新element节点,主要是更新props和子节点,内部还涉及到动态patch更新t
const patchProps = () => {...} // 处理元素的props属性
const processFragment = () => {...} // 处理Fragment
const processComponent = () => {...} // 处理组件,内部会调用 mountComponent
const mountComponent = () => {...} // 挂载组件
const updateComponent = () => {...} // 更新组件
const setupRenderEffect = () => {...} // 创建一个响应式的渲染函数,赋值给app实例
const updateComponentPreRender = () => {...} // 更新组件信息
const patchChildren = () => {...} // 更新子节点
const patchKeyedChildren = () => {...} // diff,数组子节点发生变更,主要是,更新、删除、添加、移动几种方式处理
const move = () => {...} // 移动或插入子节点
const unmount = () => {...} // 卸载
const remove = () => {...} // 删除元素或vnode
const removeFragment = () => {...} // 删除Fragment
const unmountComponent = () => {...} // 卸载所有子组件
const unmountChildren = () => {...} // 卸载所有子节点
const getNextHostNode = () => {...} // 获取下一个兄弟节点
const render = (vnode, container) => {
if (vnode == null) {
// 没有vnode,卸载
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
}
else {
// 创建或更新组件
patch(container._vnode || null, vnode, container);
}
// 队列检测,如果有值就刷新(vue的调度系统机制,类似于Fiber)
flushPostFlushCbs();
// 缓存vnode
container._vnode = vnode;
};
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
看到这,一脸嫌弃,这么多的处理。。。
我们来看几个重点的方法吧。
processText
处理文本:
const processText = (n1, n2, container, anchor) => {
if (n1 == null) {
// 旧节点不存在,创建一个新的文本节点
hostInsert((n2.el = hostCreateText(n2.children)), container, anchor)
} else {
const el = (n2.el = n1.el)
if (n2.children !== n1.children) {
// 两个内容不相同,覆盖文本,n2.children 就是字符串文本。
hostSetText(el, n2.children)
}
}
}
processElement
处理element,首次挂载,其他就是更新:
const processElement = ( n1, n2, container, ... ) => {
isSVG = isSVG || n2.type === 'svg'
if (n1 == null) {
// 初始化挂载节点element
mountElement( n2, container, ...)
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
}
}
mountElement
挂载element:
const mountElement = (
vnode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
let el
let vnodeHook
const {
type,
props,
shapeFlag,
transition,
scopeId,
patchFlag,
dirs,
} = vnode
{
// 创建dom节点
el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is)
// 处理文本节点
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',
optimized || !!vnode.dynamicChildren
)
}
// 调用指令hook的生命周期方法:created
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'created')
}
// vnode的props:例如 { id: 'search', type:'button' }
if (props) {
for (const key in props) {
if (!isReservedProp(key)) {
hostPatchProp(el, key, null,props[key], ...)
}
}
}
//设置dom的一些attr属性
setScopeId(el, scopeId, vnode, parentComponent)
}
// 将虚拟节点缓存给dom,且不能被枚举出来。
{
Object.defineProperty(el, '__vnode', {
value: vnode,
enumerable: false,
})
Object.defineProperty(el, '__vueParentComponent', {
value: parentComponent,
enumerable: false,
})
}
if (dirs) {
// 调用指令hook的生命周期方法: beforeMount
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
}
// 把创建的 element,插入到节点中
hostInsert(el, container, anchor)
if (
(vnodeHook = props && props.onVnodeMounted) ||
needCallTransitionHooks ||
dirs
) {
queuePostRenderEffect(() => {
// 挂载组件的effect队列,数据发生改变会执行该函数
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
needCallTransitionHooks && transition.enter(el)
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
}, parentSuspense)
}
}
整个函数做这几件事情:
- 创建 DOM 元素节点(hostCreateElement)
- 处理 props
- 处理 children 元素(此方式与组件处理类似,递归调用 patch 流程)
- 挂载 DOM 元素到 container 上
- 调用了钩子的一些生命周期方法
- 对 transition 的单独处理
patchElement
更新element节点。
const patchElement = (
n1,
n2,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const el = (n2.el = n1.el)
let { patchFlag, dynamicChildren, dirs } = n2
if (patchFlag > 0) {
/**
* patchFlag的存在意味着该元素的呈现代码
* 在这个路径中,旧节点和新节点的形状是相同的
*/
if (patchFlag & 16 /* FULL_PROPS */) {
// 更新动态属性key
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
} else {
if (patchFlag & 2 /* CLASS */) {
if (oldProps.class !== newProps.class) {
// 更新class
hostPatchProp(el, 'class', null, newProps.class, isSVG)
}
}
if (patchFlag & 4 /* STYLE */) {
// 更新 style
hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
}
if (patchFlag & 8 /* PROPS */) {
const propsToUpdate = n2.dynamicProps
/**
* propsToUpdate是 onClick | onUpdate:modelValue 这些。
* 示例:<polygon :points="points"></polygon> propsToUpdate === ["points"]
*/
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const prev = oldProps[key]
const next = newProps[key]
// 这里的next可能是 string number function object boolean
// 比较不同就更新
if (
next !== prev ||
(hostForcePatchProp && hostForcePatchProp(el, key))
) {
hostPatchProp(el, key,prev,next, ...)
}
}
}
}
// 文本更新
if (patchFlag & 1 /* TEXT */) {
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children)
}
}
}
}
patchChildren
更新子节点:
const patchChildren = (
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized = false
) => {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0 // 旧节点
const c2 = n2.children
const { patchFlag, shapeFlag } = n2 // 新节点
// fast path
if (patchFlag > 0) {
if (patchFlag & 128 /* KEYED_FRAGMENT */) {
patchKeyedChildren(
c1,
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
} else if (patchFlag & 256 /* UNKEYED_FRAGMENT */) {
// unkeyed
patchUnkeyedChildren(
c1,
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
}
}
// children有3种可能:文本、数组或没有children。
if (shapeFlag & 8 /* TEXT_CHILDREN */) {
if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
// 新节点是文本,旧节点是数组,直接删除。
unmountChildren(c1, parentComponent, parentSuspense)
}
// 新节点是文本,和旧节点不相同,直接替换。
if (c2 !== c1) {
hostSetElementText(container, c2)
}
} else {
if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
// 新节点和旧节点都是数组,diff比较
patchKeyedChildren(
c1,
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// 新节点不是数组,旧节点是数组,删除子节点
unmountChildren(c1, parentComponent, parentSuspense, true)
}
} else {
// 子节点是文本或null
// 新节点是数组或null
if (prevShapeFlag & 8 /* TEXT_CHILDREN */) {
// 替换空文本
hostSetElementText(container, '')
}
// 挂载一个新的数组子节点
if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
mountChildren(
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
}
总结
- 新节点是文本
- 1 - 旧节点是数组,删除所有字节点,插入文本
- 2 - 旧节点不是是文本,替换文本
- 3 - 旧节点是空,添加文本
- 新节点是数组
- 1 - 旧节点是数组,diff比较子节点
- 2 - 旧节点不是数组,删除所有子节点,挂载新的子节点
- 3 - 旧节点是空,挂载新的子节点
- 新节点是空
- 1 - 直接删除
processFragment
处理 Fragment
const processFragment = (
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 创建两个空节点
const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''))
const fragmentEndAnchor = (n2.anchor = n1
? n1.anchor
: hostCreateText(''))
let { patchFlag, dynamicChildren } = n2
if (patchFlag > 0) {
optimized = true
}
if (n1 == null) {
// 初始化挂载
hostInsert(fragmentStartAnchor, container, anchor)
hostInsert(fragmentEndAnchor, container, anchor)
// 挂载子节点,这里只能是数组的子集
mountChildren(
n2.children,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// 更新Fragment
if (
patchFlag > 0 &&
patchFlag & 64 /* STABLE_FRAGMENT */ &&
dynamicChildren &&
n1.dynamicChildren
) {
// 更新整个block,内部会遍历patch
patchBlockChildren(
n1.dynamicChildren,
dynamicChildren,
container,
parentComponent,
parentSuspense,
isSVG
)
} else {
// 更新子节点,遍历patch
patchChildren(
n1,
n2,
container,
fragmentEndAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
createComponentInstance
创建组件实例。
function createComponentInstance(vnode, parent, suspense) {
const type = vnode.type
// inherit parent app context - or - if root, adopt from root vnode
// 从父组件实例继承应用上下文,如果没有父组件,说明是根组件实例,
// 直接从根级vnode中获取应用上下文
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance = {
uid: uid$2++,
vnode, // 当前组件vnode
type,
parent, // 父组件实例
appContext,
root: null, // 根组件实例
next: null,
subTree: null, // 组件的渲染vnode,由组件的render函数生成
update: null, // 组件内容挂载 / 更新到视图的执行回调
render: null, // 组件的render函数
proxy: null,
exposed: null,
withProxy: null,
effects: null, // 组件相关的依赖集合
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: [],
// local resovled assets
components: null, // 注册在当前组件下组件options
directives: null, // 注册在当前组件下的指令
// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
// emit
emit: null,
emitted: null,
// state
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ, // 用于挂载setup执行的结果
setupContext: null,
// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null,
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
// 生命周期钩子及相应的标志位
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
}
{
instance.ctx = createRenderContext(instance)
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
{
devtoolsComponentAdded(instance)
}
return instance
}
setupComponent
组件初始化。
这里的setup就是我们在组件里面的setup函数。
function setupComponent(instance, isSSR = false) {
isInSSRComponentSetup = isSSR
const { props, children, shapeFlag } = instance.vnode
// 是否是状态型组件
const isStateful = shapeFlag & 4 /* STATEFUL_COMPONENT */
// 初始化组件属性、插槽
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
// 仅为状态型组件挂载setup信息,非状态型组件仅为纯UI展示不需要挂载状态信息
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
// 初始化组件props
function initProps(
instance,
rawProps,
isStateful, // 位标志比较结果
isSSR = false
) {
const props = {}
const attrs = {}
// 定义内部属性并通过defineProperty设置属性不可枚举
def(attrs, InternalObjectKey, 1)
// 初始化外部定义属性
setFullProps(instance, rawProps, props, attrs)
// 校验prps
validateProps(props, instance)
if (isStateful) {
// 状态型组件将处理后的属性挂载到组件实例上
// 属性做浅响应式转化
instance.props = isSSR ? props : shallowReactive(props)
} else {
if (!instance.type.props) {
instance.props = attrs
} else {
instance.props = props
}
}
instance.attrs = attrs
}
// 状态型组件生成setup结果并进行信息挂载
function setupStatefulComponent(instance, isSSR) {
// 组件options Object,也就是vnode.type
const Component = instance.type
{
if (Component.name) {
// 校验组件名是否合法
validateComponentName(Component.name, instance.appContext.config)
}
if (Component.components) {
// 批量校验组件名
const names = Object.keys(Component.components)
for (let i = 0; i < names.length; i++) {
validateComponentName(names[i], instance.appContext.config)
}
}
if (Component.directives) {
// 批量校验指令名是否合法
const names = Object.keys(Component.directives)
for (let i = 0; i < names.length; i++) {
validateDirectiveName(names[i])
}
}
}
instance.accessCache = Object.create(null)
// 为组件实例创建渲染代理
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
{
exposePropsOnRenderContext(instance)
}
const { setup } = Component
if (setup) {
// 创建setup上下文并挂载到组件实例上
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
// 记录当前正在初始化的组件
currentInstance = instance
// 执行setup前暂停依赖收集,以免产生不可预测的问题
pauseTracking()
// 执行setup并获得安装结果信息,setup执行结果就是我们定义的响应式数据、函数、钩子等
const setupResult = callWithErrorHandling(
setup,
instance,
0 /* SETUP_FUNCTION */,
[shallowReadonly(instance.props), setupContext]
)
// setup执行完毕恢复依赖收集
resetTracking()
// 当前实例重置
currentInstance = null
// setup执行结果
if (isPromise(setupResult)) {
if (isSSR) {
return setupResult.then((resolvedResult) => {
handleSetupResult(instance, resolvedResult)
})
} else {
instance.asyncDep = setupResult
}
} else {
// 将setup的执行结果挂载到组件实例上
handleSetupResult(instance, setupResult)
}
} else {
finishComponentSetup(instance)
}
}
// 处理setup执行结果,将结果挂载到组件实例,setup执行结果可以是内联render函数、Object
function handleSetupResult(instance, setupResult, isSSR) {
if (isFunction(setupResult)) {
{
instance.render = setupResult
}
} else if (isObject(setupResult)) {
if (isVNode(setupResult)) {
warn(
`setup() should not return VNodes directly - ` +
`return a render function instead.`
)
}
{
instance.devtoolsRawSetupState = setupResult
}
// 为setup结果做代理拦截,并将代理结果挂载到组件的setupState上
instance.setupState = proxyRefs(setupResult)
{
exposeSetupStateOnRenderContext(instance)
}
} else if (setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
}`
)
}
finishComponentSetup(instance)
}
mountComponent
挂载组件。
const mountComponent = (initialVNode, container, anchor, ...) => {
// 1.创建组件实例,见上面的方法
const instance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 2.设置组件实例,见上面的方法
setupComponent(instance)
// 3.设置并且运行带有副作用的渲染函数,见下面的方法
setupRenderEffect(instance, ...)
}
该方法做了3件重要的事情:
- 创建组件实例
- 设置组件实例,挂载setup,包括里面的钩子
- 生成渲染的effect
setupRenderEffect
运行带有副作用的render。
const setupRenderEffect = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
/*
创建一个响应式的渲染函数,当组件内容有数据变化时,会执行此方法。
这里会立即执行一次
*/
instance.update = effect(function componentEffect() {
// 首次挂载,初始化渲染
if (!instance.isMounted) {
let vnodeHook
const { el, props } = initialVNode
const { bm, m, parent } = instance
// console.log('props', props)
// beforeMount hook 执行beforeMount(挂载之前)钩子函数,是个数组。
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
// render 开始性能监控
startMeasure(instance, `render`)
// 生成子树结构
const subTree = (instance.subTree = renderComponentRoot(instance))
endMeasure(instance, `render`)
startMeasure(instance, `patch`)
// 将子树渲染到container中
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
endMeasure(instance, `patch`)
initialVNode.el = subTree.el
// 执行mount(挂载之后)钩子函数
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
//激活钩子保持根系活力。
//#1742 首次渲染后必须访问激活的钩子
//因为钩子可能会被children注入,所以要保持生命周期
const { a } = instance
if (
a &&
initialVNode.shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */
) {
queuePostRenderEffect(a, parentSuspense)
}
instance.isMounted = true
// 清空
initialVNode = container = anchor = null
} else {
// updateComponent 更新组件,非首次
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook
pushWarningContext(next || instance.vnode)
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// render 性能监控
startMeasure(instance, `render`)
const nextTree = renderComponentRoot(instance)
endMeasure(instance, `render`)
const prevTree = instance.subTree
instance.subTree = nextTree
startMeasure(instance, `patch`)
// 把对应的subTree渲染到container中,子树可能是element、text、component等
patch(
prevTree,
nextTree,
// 如果在传送中,父级可能已经改变了
hostParentNode(prevTree.el),
// 如果在碎片中,它可能已经改变了
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
endMeasure(instance, `patch`)
next.el = nextTree.el
// updated 钩子
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
}
}, createDevEffectOptions(instance)) // 创建批量更新方法
}
// 这里的scheduler,就是Trigger派发时执行的 effect.options.scheduler
// 具体怎么派发的,我们下章讲。
function createDevEffectOptions(instance) {
return {
scheduler: queueJob,
allowRecurse: true,
onTrack: instance.rtc ? (e) => invokeArrayFns(instance.rtc, e) : void 0,
onTrigger: instance.rtg ? (e) => invokeArrayFns(instance.rtg, e) : void 0,
}
}
该方法的功能解析:
- 首页挂载,初始化
- 执行 beforeMount 钩子
- render 性能监控
- 生成对应的 subTree
- 执行 patch 将 subTree 渲染到 container
- 执行 mount 钩子
- 执行 keep-alive 的 activated 钩子
- 非首次,更新
- 更新组件信息
- 执行 beforeUpdate 钩子
- 执行 patch 将 subTree 渲染到 container,更新会有新旧diff
- 执行 update 钩子
unmount
卸载:
卸载就是把不能自动回收的信息进行手动释放,防止内存泄露。
// 卸载
const unmount = (
vnode, // 需卸载的vnode
parentComponent,
parentSuspense,
doRemove = false, // 是否需要从dom树移除对应的节点
optimized = false
) => {
const {
type,
props,
ref,
children,
dynamicChildren,
shapeFlag, // vnode形态标识
patchFlag, // vnode打补丁优化类型标识
dirs,
} = vnode
// 对于非空vnode引用ref,进行卸载前重置,GC回收过期ref信息
if (ref != null) {
setRef(ref, null, parentSuspense, null)
}
if (shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
parentComponent.ctx.deactivate(vnode)
return
}
// 是否需要执行vnode中的自定义指令
const shouldInvokeDirs = shapeFlag & 1 /* ELEMENT */ && dirs
let vnodeHook
// 执行props中传入的即将卸载钩子
if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
invokeVNodeHook(vnodeHook, parentComponent, vnode)
}
if (shapeFlag & 6 /* COMPONENT */) {
// 组件vnode卸载逻辑
unmountComponent(vnode.component, parentSuspense, doRemove)
} else {
if (shapeFlag & 128 /* SUSPENSE */) {
// 卸载suspense,因为特殊有自动的一套逻辑
vnode.suspense.unmount(parentSuspense, doRemove)
return
}
if (shouldInvokeDirs) {
// 执行自定义指令的生命周期
invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
}
if (
dynamicChildren &&
(type !== Fragment ||
(patchFlag > 0 && patchFlag & 64) /* STABLE_FRAGMENT */)
) {
// unmountChildren就是把传入的children遍历执行一遍unmount
unmountChildren(
dynamicChildren,
parentComponent,
parentSuspense,
false,
true
)
} else if (
(type === Fragment &&
(patchFlag & 128 /* KEYED_FRAGMENT */ ||
patchFlag & 256) /* UNKEYED_FRAGMENT */) ||
(!optimized && shapeFlag & 16) /* ARRAY_CHILDREN */
) {
// 做对应的卸载操作
unmountChildren(children, parentComponent, parentSuspense)
}
if (
shapeFlag & 64 /* TELEPORT */ &&
(doRemove || !isTeleportDisabled(vnode.props))
) {
// 卸载teleport内置组件和suspense类似,也有自己的一套mount、unmount逻辑
vnode.type.remove(vnode, internals)
}
// vnode对应整棵子树的信息都已卸载回收完毕后,
// 将vnode对应的dom子树整体从dom树中移除掉,
// 此时移除dom子树是安全的,无内存泄漏风险
if (doRemove) {
remove(vnode)
}
}
}
该方法主要做了3件事:
- 找到所有动态节点,将GC无法自动回收的手动清理掉
- 执行卸载生命周期钩子
- 移除dom
unmountComponent
卸载所有子组件:
const unmountComponent = (instance, parentSuspense, doRemove) => {
if (instance.type.__hmrId) {
unregisterHMR(instance)
}
const { bum, effects, update, subTree, um } = instance
// beforeUnmount 钩子
if (bum) {
invokeArrayFns(bum)
}
// 组件的effect,需要统一回收处理,并取消effect的激活状态
if (effects) {
for (let i = 0; i < effects.length; i++) {
stop(effects[i])
}
}
if (update) {
stop(update)
// 卸载整个subTree,subTree可以理解为渲染vnode,对实际渲染的vnode自然要做对应的unmount操作
unmount(subTree, instance, parentSuspense, doRemove)
}
// unmounted 钩子,推入任务队列,等待渲染任务执行完毕后批量执行
if (um) {
queuePostRenderEffect(um, parentSuspense)
}
queuePostRenderEffect(() => {
instance.isUnmounted = true
}, parentSuspense)
//在挂起暂挂中包含异步dep的组件
//它的异步dep解析。这将从悬念中移除dep,并且
//如果这是最后一个dep,立即解决悬念。
if (
parentSuspense &&
parentSuspense.pendingBranch &&
!parentSuspense.isUnmounted &&
instance.asyncDep &&
!instance.asyncResolved &&
instance.suspenseId === parentSuspense.pendingId
) {
parentSuspense.deps--
if (parentSuspense.deps === 0) {
parentSuspense.resolve()
}
}
{
devtoolsComponentRemoved(instance)
}
}
😰 还有好多,有空继续更新。。。
createAppAPI
不用气馁,精彩还在后面!!!
我们回到 createRenderer ,看到 createApp 是 createAppAPI 生成的。
😀 来来来,继续往下看:
function createAppContext() {
return {
app: null,
config: {
isNativeTag: NO, // 默认 const NO = () => false
performance: false,
globalProperties: {},
optionMergeStrategies: {},
isCustomElement: NO,
errorHandler: undefined,
warnHandler: undefined,
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
}
}
// 是个高阶函数,返回createApp
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
// rootComponent 就是传入的options
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
// 这么创建一个app实例,并返回。这就是我们看到的那个app
const app = (context.app = {
_uid: uid$1++,
_component: rootComponent,
_props: rootProps,
_container: null,
_context: context, // 就是上面的createAppContext
version,
get config() {
return context.config
},
set config(v) {
{
warn(
`app.config cannot be replaced. Modify individual options instead.`
)
}
},
// 这里加载插件,和vue2的区别是,vue2加载插件是全局的,这里是每个createApp都独立。
use(plugin, ...options) {
// 判断是否加载过了
if (installedPlugins.has(plugin)) {
warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
// 插件入口install
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
}
return app
},
// 引入mixin
mixin(mixin) {
{
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin)
// global mixin with props/emits de-optimizes props/emits
// normalization caching.
if (mixin.props || mixin.emits) {
context.deopt = true
}
}
}
return app
},
// 引入组件 | 加载组件(与options下的component一样)
component(name, component) {
// 校验组件命名和引入名是否一致
{
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
// 已经注册过了,出现警告
if (context.components[name]) {
warn(
`Component "${name}" has already been registered in target app.`
)
}
// 注册组件
context.components[name] = component
return app
},
// 引入指令
directive(name, directive) {
{
validateDirectiveName(name)
}
if (!directive) {
return context.directives[name]
}
if (context.directives[name]) {
warn(
`Directive "${name}" has already been registered in target app.`
)
}
context.directives[name] = directive
return app
},
// 挂载
mount(rootContainer, isHydrate) {
// rootContainer 就是 #app的dom节点
// 判断是否已挂载
if (!isMounted) {
// 创建虚拟节点
const vnode = createVNode(rootComponent, rootProps)
// 在根VNode上存储context
vnode.appContext = context
// 热更新
{
context.reload = () => {
render(cloneVNode(vnode), rootContainer)
}
}
if (isHydrate && hydrate) {
hydrate(vnode, rootContainer)
} else {
// 这里判断渲染方式,浏览器是render。
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer
rootContainer.__vue_app__ = app
// 加载开发工具
{
devtoolsInitApp(app, version)
}
return vnode.component.proxy
} else {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},
// 卸载
unmount() {
if (isMounted) {
render(null, app._container)
{
devtoolsUnmountApp(app)
}
} else {
warn(`Cannot unmount an app that is not mounted.`)
}
},
// vue的注入
provide(key, value) {
if (key in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
`It will be overwritten with the new value.`
)
}
context.provides[key] = value
return app
},
})
return app
}
}
干货也太干了。我们用一张图说明调用逻辑:
图片来自 @望舒(mobai-epd1x)
手写流程
const createAppAPI = () => {
}
const createRenderer = (options) => {
const render = (node, container) => {
return { render, createApp: createAppAPI(render) }
}
}
const renderer = createRenderer({})
const Vue = {
createApp(options){
return renderer.createApp(options)
}
}