我们从下面一个最简单的例子说起
<div id="app">
<h1>{{title}}</h1>
</div>
<script>
const {createApp} = Vue
createApp({
data() {
return {
title: 'coboy'
}
}
}).mount('#app')
</script>
这其中 vue3 到底都干了些什么呢?
runtime-dom中的ensureRenderer
我们查看源码可以知道 createApp 来自 runtime-dom/src/index.ts
// 用户实际调用的
export const createApp = ((...args) => {
// 先获取渲染器
const app = ensureRenderer().createApp(...args)
// ... 省略
return app
}) as CreateAppFunction<Element>
在这段源码中我们可以看到在用户实际调用的 createApp 函数中,先获取了一个渲染器,执行了渲染器函数 ensureRenderer 返回的结果当中也包含一个 createApp 函数
我们继续追踪 ensureRenderer 函数里面干了什么
function ensureRenderer() {
// 已经存在了渲染器,就返回,否则就创建一个渲染器,那么初始化的时候,肯定走的后面部份代码
return (
renderer || ((renderer = createRenderer < Node), Element > rendererOptions)
);
}
继续查看 createRenderer 函数 我们查看源码可以知道 createRenderer 来自 runtime-core/src/renderer.ts
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer < HostNode, HostElement > options;
}
我们追查到了 baseCreateRenderer 函数,然后看看 baseCreateRenderer 函数
runtime-core中的baseCreateRenderer
我们查看源码可以知道 baseCreateRenderer 来自 runtime-core/src/renderer.ts
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// 代码太多,将近2000行,省略
}
然后我们可以看到这个创建渲染器的基础函数是非常多代码的,有将近 2000 行代码,是 vue3 源码库中目前最大的函数,据说当初也有人建议尤大大把这个函数拆分一下,但被尤大大拒绝了,尤大大认为没必要过度设计,这函数的职责就是做渲染器的,职责非常明确,不用再分
所以从这个故事中我们也可以得知,这个函数就是做渲染器一系列工作的,具体内容可以先不去关注,我们先只需要关注一下这个函数的返回值
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
// 代码太多,将近2000行,省略具体细节
// 重点函数,只要vnode转换成dom都要使用patch函数
const patch: PatchFn = () => {}
// ...
// 重点函数
const processComponent = () => {}
// 重点函数
const mountComponent: MountComponentFn = () => {}
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {}
// 重点函数
const setupRenderEffect: SetupRenderEffectFn = () => {}
// ...
const render: RootRenderFunction = (vnode, container, isSVG) => {}
let hydrate: ReturnType<typeof createHydrationFunctions>[0] | undefined
// 返回createApp是由createAppAPI函数的执行结果
return {
render, // 相当于react里面的ReactDom.render, 不同组件内部的render,此render的作用是传入vnode,转换成dom,追加到宿主
hydrate, // 用于SSR
createApp: createAppAPI(render, hydrate)
}
}
然后初始化渲染器后返回一个渲染器,执行 createApp 函数
// 先获取渲染器
const app = ensureRenderer().createApp(...args);
我们从上面的创建渲染器的 baseCreateRenderer 函数返回的结果中可以知道 createApp 是由 createAppAPI 函数执行的返回结果
渲染器实例中的createAppAPI
所以我们去 createAppAPI 里面看看都干了什么 我们查看源码可以知道 baseCreateRenderer 来自 runtime-core/src/apiCreateApp.ts createAppAPI初始化的时候接收了一个render方法,返回一个createApp方法
// 此方法是createApp的工厂方法
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
// 返回App实例
return function createApp(rootComponent, rootProps = null) {
if (rootProps != null && !isObject(rootProps)) {
rootProps = null
}
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
// 外面得到的APP实例
const app: App = (context.app = {
get config() {
return context.config
},
set config(v) {
},
// 插件应用方法
use(plugin: Plugin, ...options: any[]) {
return app
},
//兼容vue2的mixin方法
mixin(mixin: ComponentOptions) {
return app
},
// 组件方法
component(name: string, component?: Component): any {
return app
},
// 指令方法
directive(name: string, directive?: Directive) {
return app
},
// 初始化,这里最重要的就是这个mount方法
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
// 初始化的时候,还买挂载,走这里
if (!isMounted) {
// 构建根组件虚拟dom
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
if (isHydrate && hydrate) {
// 服务端渲染走这里
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// spa程序走这里
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
} else if (__DEV__) {
}
},
unmount() {
if (isMounted) {
render(null, app._container)
delete app._container.__vue_app__
} else if (__DEV__) {
}
},
provide(key, value) {
context.provides[key as string] = value
return app
}
})
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}
return app
}
}
createApp 进行初始化传进来了一个 render(利用闭包特性,等于设置一个未来可以更新的机制),将来造一个 vnode 传给 render 并把在 mount 的时候接收到的 rootComponent 宿主元素也传进去,
重写mount方法
我们走了一遍然后又回到
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
const {mount} = app
// 重写mount方法,也是最外面用户调用mount方法的时候,就是调用这个函数
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// 获取模板内容
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
// 重点步骤,把模板内容赋值给component.template
component.template = container.innerHTML
}
// 重点步骤,挂载前清空模板内容
container.innerHTML = ''
// 这里的mount方法就是createAppAPI里createApp中的mount方法
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
return app
}) as CreateAppFunction<Element>
createAppAPI 里的mount方法
利用闭包的特性我们最终执行的 mount 方法是刚刚上面的 createAppAPI 里面的 mount 方法
关键代码
// 初始化,这里最重要的就是这个mount方法
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
// 初始化的时候,还买挂载,走这里
if (!isMounted) {
// 创建根组件虚拟dom
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
// 服务器端渲染
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// 初始化走这里
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return vnode.component!.proxy
} else if (__DEV__) {
}
},
mount 方法里初始化最终走的是 render 函数,这个 render 在那个 2000 行代码的渲染器里
关键代码
// 接收vnode,转为dom,并追加到宿主container
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
} else {
// 初始化走这里
// 只要vnode转换成dom都要使用patch函数
// 初始化时container._vnode为null,走初始化的逻辑,不会走diff
patch(container._vnode || null, vnode, container, null, null, null, isSVG);
}
flushPostFlushCbs();
container._vnode = vnode;
};
这个 patch 也在那个 2000 行代码的渲染器里
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = false
) => {
if (n1 && !isSameVNodeType(n1, n2)) {
}
if (n2.patchFlag === PatchFlags.BAIL) {
}
const {type, ref, shapeFlag} = n2
switch (type) {
case Text:
break
case Comment:
break
case Static:
break
case Fragment:
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 初始化走的是这里
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
} else if (__DEV__) {
}
}
为什么初始化走 else if (shapeFlag & ShapeFlags.COMPONENT) 这个分支呢
因为在 createAppAPI 里面的 mount 方法创建了根组件的虚拟 DOM,所以 ShapeFlags.COMPONENT 为真
// 初始化的时候,还未挂载,走这里
if (!isMounted) {
// 创建根组件虚拟dom
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
}
这个 processComponent 函数也在那 2000 行的渲染器函数里
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)
}
}
mountComponent,挂载组件函数
终于看到了熟悉的函数 mountComponent,挂载组件函数
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 1.创建组件实例
const instance: ComponentInternalInstance = (initialVNode.component =
createComponentInstance(initialVNode, parentComponent, parentSuspense));
if (isKeepAlive(initialVNode)) {
}
// 2.组件实例安装,相当于组件初始化,相当于vue2的this._init 实例属性,方法初始化,数据响应式,两个生命周期的钩子
// 属性声明,响应式等等
setupComponent(instance);
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
}
// 更新机制.安装渲染函数副作用,相当于updateComponent+watcher
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
);
};
setupComponent函数
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR;
const { props, children, shapeFlag } = instance.vnode;
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT;
// 初始化Props
initProps(instance, props, isStateful, isSSR);
// 初始化slots
initSlots(instance, children);
// setup数据状态的处理
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined;
isInSSRComponentSetup = false;
return setupResult;
}
setup数据状态的处理 setupStatefulComponent函数
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 此处ctx就是vue2中大家熟悉的this,组件实例
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
// 2. call setup()
const { setup } = Component
// 如果设置了setup函数
if (setup) {
// setupContext上下文
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
currentInstance = instance
pauseTracking() // 树摇优化
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking() // 重置树摇优化
currentInstance = null
// setup有可能返回的是一个Promise
if (isPromise(setupResult)) {
if (isSSR) {
} else if (__FEATURE_SUSPENSE__) {
instance.asyncDep = setupResult
} else if (__DEV__) {
}
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
}
}
finishComponentSetup函数
注意不管有没有设置setup函数最终都会执行finishComponentSetup函数
function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
if (__NODE_JS__ && isSSR) {
} else if (!instance.render) {
// 如果没有render函数就编译一下
if (compile && Component.template && !Component.render) {
Component.render = compile(Component.template, {
isCustomElement: instance.appContext.config.isCustomElement,
delimiters: Component.delimiters
})
}
instance.render = (Component.render || NOOP) as InternalRenderFunction
if (instance.render._rc) {
instance.withProxy = new Proxy(
instance.ctx,
RuntimeCompiledPublicInstanceProxyHandlers
)
}
}
// 兼容vue2.0
if (__FEATURE_OPTIONS_API__) {
currentInstance = instance
applyOptions(instance, Component)
currentInstance = null
}
}
applyOptions 函数,处理vue2的api
export function applyOptions(
instance: ComponentInternalInstance,
options: ComponentOptions,
deferredData: DataFn[] = [],
deferredWatch: ComponentWatchOptions[] = [],
asMixin: boolean = false
) {
const {
// composition
mixins,
extends: extendsOptions,
// state
data: dataOptions,
computed: computedOptions,
methods,
watch: watchOptions,
provide: provideOptions,
inject: injectOptions,
// assets
components,
directives,
// lifecycle 可以看到vue3的生命周期函数是没有beforeCreate、created函数的,用setup取代
beforeMount,
mounted,
beforeUpdate,
updated,
activated,
deactivated,
beforeDestroy,
beforeUnmount,
destroyed,
unmounted,
render,
renderTracked,
renderTriggered,
errorCaptured
} = options
const publicThis = instance.proxy!
if (beforeMount) {
// 我们删除了很多其他代码逻辑,单独看看这个函数,很明显我们可以beforeMount的上下文绑定是的instance.proxy
onBeforeMount(beforeMount.bind(publicThis))
}
}
setupRenderEffect函数
该方法会将渲染函数封装成一个副作用函数,并立即执行,执行完毕界面渲染出现,当依赖的响应式数据发生变化时,会重新执行
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// 类似于useEffect(fn,[])
instance.update = effect(function componentEffect() {
// 首次isMounted是false,走初始化流程
if (!instance.isMounted) {
// 首先获取组件vnode,其实就是调用组件render
// 这次调用触发了依赖收集
// subTree可以理解为当前组件的子树
const subTree = (instance.subTree = renderComponentRoot(instance))
if (el && hydrateNode) {
} else {
// 向下递归更新
// 首次是完整递归创建
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
}
instance.isMounted = true
} else {// 更新走这里
}
}
}
总结:
vue3里没有Watcher,Dep,变得非常纯粹,就是响应式数据和它相关副作用函数的之间的关系
画一张流程图来说明vue3的初始化
结合流程图来说就是,我们从一个全局变量 Vue 中导出了一个 createApp 的函数,然后用这个 createApp 创建了一个实例,createApp主要是先创建了一个渲染器,而渲染器是由runtime-dom里的ensureRenderer自定义web平台渲染器调用runtime-core里的baseCreateRenderer基础的渲染器创建返回一个渲染器实例,再由这个渲染器实例里的createApp方法创建一个app实例,这个app实例里有一个 mount 方法,最后使用这个 mount 方法解析转换模板内容,最后再挂载到了宿主节点上