从入口开始
回忆下vue如何启动
const app = createApp(App)
app.mount('#app')
很清晰我们先调用createApp函数创建app对象,接着调用mount方法将组件挂载到id位app的container中。
以下分析只考虑在浏览器环境下情况
createApp函数
做什么:
createApp返回App对象
export interface App<HostElement = any> {
version: string
config: AppConfig
use(plugin: Plugin, ...options: any[]): this
mixin(mixin: ComponentOptions): this
component(name: string): Component | undefined
component(name: string, component: Component): this
directive(name: string): Directive | undefined
directive(name: string, directive: Directive): this
mount(
rootContainer: HostElement | string,
isHydrate?: boolean,
isSVG?: boolean
): ComponentPublicInstance
unmount(): void
provide<T>(key: InjectionKey<T> | string, value: T): this
// internal, but we need to expose these for the server-renderer and devtools
_uid: number
_component: ConcreteComponent
_props: Data | null
_container: HostElement | null
_context: AppContext
_instance: ComponentInternalInstance | null
/**
* v2 compat only
*/
filter?(name: string): Function | undefined
filter?(name: string, filter: Function): this
/**
* @internal v3 compat only
*/
_createRoot?(options: ComponentOptions): ComponentPublicInstance
}
mount函数
做什么?
创建vnode并将最后的dom挂载到container中。
如何进行?
function mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
) {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
...
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
...
}
}
render函数
函数内部通过createVNode创建vnode,通过render函数创建dom并挂载到container中,可以看到一个非常重要的函数render。
在renderer.ts文件中我们看到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)
}
flushPostFlushCbs()
container._vnode = vnode
}
render函数看上去很简单,如果vnode为空且container已经有vnode则 意味着新树结构需要删除节点,否则调用patch函数更新新旧树,最后则是执行post callback已经更新container的vnode指针。
patch函数
pathc是render函数中一个重要步骤,他包含了创建dom节点,diff比较并且更新dom。
进入patch函数后,由于需要更新的vnnode是component类型
=》进入processComponent函数,当旧节点为空当前且当前节点不为keepalive
=》进入mountComponent此函数创建ComponentInternalInstance
=》接着instance传入setupComponent(instance)
setupComponent函数
setupComponent是对组件初始化的开始
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
函数逻辑很简单先是初始化props和slots接着执行setupStatefulComponent
setupStatefulComponent函数
setupStatefulComponent会执行setup函数并根据返回值做进一步处理。
if (isFunction(setupResult)) {
// setup returned an inline render function
if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
// when the function's name is `ssrRender` (compiled by SFC inline mode),
// set it as ssrRender instead.
instance.ssrRender = setupResult
} else {
instance.render = setupResult as InternalRenderFunction
}
} else if (isObject(setupResult)) {
present.instance.setupState = proxyRefs(setupResult)
} else if (__DEV__ && setupResult !== undefined) {
...
}
finishComponentSetup(instance, isSSR)
1.当返回值为函数时做被当做render函数
2.当返回值为Object类型时做被当做state值
接着执行finishComponentSetup函数
finishComponentSetup函数
finishComponentSetup会判断当前是否会有render函数如果没有则查看当前是否有template如果有则将其编译成render函数,如果既没有render也没有template则赋给以个空函数。
最后通过applyOptions初始化option中属性。
applyOptions函数
export function applyOptions(instance: ComponentInternalInstance) {
const options = resolveMergedOptions(instance)
const publicThis = instance.proxy! as any
const ctx = instance.ctx
// do not cache property access on public proxy during state initialization
shouldCacheAccess = false
// call beforeCreate first before accessing other options since
// the hook may mutate resolved options (#2791)
if (options.beforeCreate) {
callHook(options.beforeCreate, instance, LifecycleHooks.BEFORE_CREATE)
}
const {
// state
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
// lifecycle
created,
beforeMount,
mounted,
beforeUpdate,
updated,
activated,
deactivated,
beforeDestroy,
beforeUnmount,
destroyed,
unmounted,
render,
renderTracked,
renderTriggered,
errorCaptured,
serverPrefetch,
// public API
expose,
inheritAttrs,
// assets
components,
directives,
filters
} = options
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
// options initialization order (to be consistent with Vue 2):
// - props (already done outside of this function)
// - inject
// - methods
// - data (deferred since it relies on `this` access)
// - computed
// - watch (deferred since it relies on `this` access)
if (injectOptions) {
resolveInjections(
injectOptions,
ctx,
checkDuplicateProperties,
instance.appContext.config.unwrapInjectedRef
)
}
if (methods) {
for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) {
// In dev mode, we use the `createRenderContext` function to define
// methods to the proxy target, and those are read-only but
// reconfigurable, so it needs to be redefined here
if (__DEV__) {
...
} else {
ctx[key] = methodHandler.bind(publicThis)
}
} else if (__DEV__) {
}
}
}
if (dataOptions) {
const data = dataOptions.call(publicThis, publicThis)
if (!isObject(data)) {
} else {
instance.data = reactive(data)
}
}
// state initialization complete at this point - start caching access
shouldCacheAccess = true
if (computedOptions) {
...
}
if (watchOptions) {
...
}
if (provideOptions) {
...
}
if (created) {
callHook(created, instance, LifecycleHooks.CREATED)
}
function registerLifecycleHook(
register: Function,
hook?: Function | Function[]
) {
if (isArray(hook)) {
hook.forEach(_hook => register(_hook.bind(publicThis)))
} else if (hook) {
register((hook as Function).bind(publicThis))
}
}
registerLifecycleHook(onBeforeMount, beforeMount)
registerLifecycleHook(onMounted, mounted)
registerLifecycleHook(onBeforeUpdate, beforeUpdate)
registerLifecycleHook(onUpdated, updated)
registerLifecycleHook(onActivated, activated)
registerLifecycleHook(onDeactivated, deactivated)
registerLifecycleHook(onErrorCaptured, errorCaptured)
registerLifecycleHook(onRenderTracked, renderTracked)
registerLifecycleHook(onRenderTriggered, renderTriggered)
registerLifecycleHook(onBeforeUnmount, beforeUnmount)
registerLifecycleHook(onUnmounted, unmounted)
registerLifecycleHook(onServerPrefetch, serverPrefetch)
if (__COMPAT__) {
if (
beforeDestroy &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_BEFORE_DESTROY, instance)
) {
registerLifecycleHook(onBeforeUnmount, beforeDestroy)
}
if (
destroyed &&
softAssertCompatEnabled(DeprecationTypes.OPTIONS_DESTROYED, instance)
) {
registerLifecycleHook(onUnmounted, destroyed)
}
}
if (isArray(expose)) {
if (expose.length) {
const exposed = instance.exposed || (instance.exposed = {})
expose.forEach(key => {
Object.defineProperty(exposed, key, {
get: () => publicThis[key],
set: val => (publicThis[key] = val)
})
})
} else if (!instance.exposed) {
instance.exposed = {}
}
}
// options that are handled when creating the instance but also need to be
// applied from mixins
if (render && instance.render === NOOP) {
instance.render = render as InternalRenderFunction
}
if (inheritAttrs != null) {
instance.inheritAttrs = inheritAttrs
}
// asset options.
if (components) instance.components = components as any
if (directives) instance.directives = directives
if (
__COMPAT__ &&
filters &&
isCompatEnabled(DeprecationTypes.FILTERS, instance)
) {
instance.filters = filters
}
}
可以清楚的看到声明周期的执行顺序为:
1.合并缓存option调用beforeCreate
2.初始化inject,method,data,computed,watch,provide
3.调用created
4.注册生命周期函数(beforeMount等)设置expose,components,directives,filters
applyOptions函数执行结束后则退出setupComponent函数,接着执行
setupRenderEffect函数
setupRenderEffect函数
1.首先调用beforMount函数
2.接着调用renderComponentRoot方法(内部调用组件的render方法)创建vnode树,并且通过patch方法将vnode树挂载到container上
3.最后调用mounted函数