本文从createApp开始简要分析都做了些什么。位置在packages/runtime-dom/src/index.ts
const app = createApp(App)
思维导图:
Mind Mapping Software: Mind Maps | MindMeister
1.createApp
初始化时首先调用了createApp ,通过createRenderer
创建了一个渲染器
export const createApp = ((...args) => {
// ensureRenderer 和渲染器相关的函数
const app = ensureRenderer().createApp(...args)
const { mount } = app
// 这里重写了mount方法
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// 获取mount所挂载的节点
// normalizeContainer用于判断el是否存在
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
component.template = container.innerHTML
// 2.x compat check
if (__COMPAT__ && __DEV__) {
for (let i = 0; i < container.attributes.length; i++) {
const attr = container.attributes[i]
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
compatUtils.warnDeprecation(
DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
null
)
break
}
}
}
}
// 渲染前清空html
container.innerHTML = ''
// 挂载元素进行渲染
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
// vue2的话,会给#app设置一个v-cloak属性,在render的时候清空掉
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
}) as CreateAppFunction<Element>
baseCreateRenderer方法非常长,包含了渲染器的所有方法,createApp是createAppAPI的返回值:
export function createRenderer(options) {
return baseCreateRenderer(options)
}
function baseCreateRenderer(options, createHydrationFns) {
// ...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
createAppAPI在packages/runtime-core/src/apiCreateApp.ts中,他返回真正的createApp,内部包含了const app = createApp(App)。这里的app 就是一个包含了许多方法的对象。因此我们可以使用app.方法名调用。
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = { ...rootComponent }
}
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = (context.app = {
use(plugin: Plugin, ...options: any[]) {
..
},
mixin(mixin: ComponentOptions) {
..
},
component(name: string, component?: Component): any {
..
},
directive(name: string, directive?: Directive) {
..
},
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
..
},
unmount() {
..
},
provide(key, value) {
..
}
})
return app
}
}
context 保存在应用实例和根VNode
上,可能是后续渲染时会用到。
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()
}
}
这就是createApp大概流程。
2.mount挂载
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
// 创建AppContext
const context = createAppContext()
// 已安装的插件
const installedPlugins = new Set()
// 是否渲染
let isMounted = false
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent as ConcreteComponent,
_props: rootProps,
_container: null,
_context: context,
version,
// ...
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
// 创建Vnode
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
// 将context挂载到根节点
vnode.appContext = context
// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}
// ssr渲染
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// 普通渲染
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(rootContainer as any).__vue_app__ = app
// 初始化devtools
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsInitApp(app, version)
}
return vnode.component!.proxy
} else if (__DEV__) {
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)\``
)
}
},
})
// ...
return app
}
}
主要逻辑就是render函数,参数vnode虚拟dom、rootContainer要挂载的位置、isSvg:boolean
vnod后边再说哦
3.render
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPreFlushCbs()
flushPostFlushCbs()
container._vnode = vnode
}
首次渲染,mount的时候已经创建了vnode,所有直接看patch函数。patch函数中用了很多位运算。判断当前vnode属于什么类型(text,comment,static,fragment或其他)(其他会判断是元素、组件、teleport、suspense)。初始化渲染一定是组件,所以直接看processComponent。
switch语句一共八种情况:
- 文本节点
- 注释节点
- 静态节点
- Fragment 节点
- 元素节点
- 组件节点
- Teleport 组件包裹的节点
- Suspense 组件包裹的节点
/**
* Note: functions inside this closure should use `const xxx = () => {}`
* style in order to prevent being inlined by minifiers.
* n1 旧节点
* n2 新节点
* anchor telport相关
*/
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = false
) => {
// ...
const { type, ref, shapeFlag } = n2
switch (type) { // type为vnode的类型,首次为component
// 文本节点
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 if ((process.env.NODE_ENV !== 'production')) {
patchStaticNode(n1, n2, container, isSVG);
}
break;
// Fragment 节点
case Fragment:
processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
break;
// 其他节点
default:
// 如果是元素节点
if (shapeFlag & 1 /* ShapeFlags.ELEMENT */) {
processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
// 如果是组件节点
else if (shapeFlag & 6 /* ShapeFlags.COMPONENT */) {
processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
}
// 如果是 Teleport 组件包裹的节点
else if (shapeFlag & 64 /* ShapeFlags.TELEPORT */) {
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);
}
// 如果是 Suspense 组件包裹的节点
else if (shapeFlag & 128 /* ShapeFlags.SUSPENSE */) {
type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);
}
// 不是以上类型的节点,报错
else if ((process.env.NODE_ENV !== 'production')) {
warn('Invalid VNode type:', type, `(${ typeof type })`);
}
}
// ...
}
processComponent具体也就判断旧节点是否为空,然后判断是否为keepAlive
组件,否则就挂载组件
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
// 挂载组件
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
// 更新组件
updateComponent(n1, n2, optimized)
}
}
下面看具体是怎么挂载组件的:
4. mountComponent
-
createComponentInstance创建组件实例
-
setupComponent安装组件
-
setupRenderEffect渲染组件
函数源码位置packages/runtime-core/src/component.ts中,大家自行查阅。
createComponentInstance返回一个对象内部维护了一些数据,用于组件渲染。
setupComponent
函数的作用是初始化组件的 props
、slots
,然后调用 setupStatefulComponent
函数,setupStatefulComponent
函数的作用是调用组件的 setup
函数;
isStatefulComponent
就是通过组件的shapeFlag
来判断组件是否是有状态的组件;
setupRenderEffect函数如下:
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// 组件的更新函数
const componentUpdateFn = () => {
// ...
};
// create reactive effect for rendering
// 创建用于渲染的响应式 effect
const effect = (instance.effect = new ReactiveEffect(componentUpdateFn, () => queueJob(update), instance.scope // track it in component's effect scope
));
// 组件的更新钩子函数
const update = (instance.update = () => effect.run());
// 组件uid,在上面出现过
update.id = instance.uid;
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
// 允许递归
// #1801,#2043 组件渲染效果应该允许递归更新
toggleRecurse(instance, true);
// 执行组件的更新函数
update();
};
这里创建了一个effect函数,而这个是ReactiveEffect的实例,也是我们在响应式原理中讲到的。副作用函数componentUpdateFn。最后执行update函数,然后会进行依赖收集,当我们修改数据时,就会触发依赖更新,从而触发effect,从而执行componentUpdateFn函数文章来源:https://www.toymoban.com/news/detail-501206.html
componentUpdateFn 函数内部实现主要两个部分,一是组件挂载,二是组件更新的逻辑。文章来源地址https://www.toymoban.com/news/detail-501206.html
到了这里,关于Vue3 从初始化到首次渲染发生了什么?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!