开始
我们都知道的vue3基础用法:
<div id="app">
<button id="show-modal" @click="showModal = true">Show Modal</button>
</div>
<script>
Vue.createApp({
components: { Modal },
data: () => ({
showModal: false
})
}).mount('#app')
</script>
那么createApp
是做了什么呢,是怎么样把虚拟DOM转换成真实DOM的呢?
带着这些疑问,我们来慢慢探究。
createApp 源码
const createApp = (...args) => {
const app = ensureRenderer().createApp(...args)
console.log('createApp.args>>>', ...args)
console.log('createApp.app>>>', app)
const { mount } = app
app.mount = (containerOrSelector) => {
const container = normalizeContainer(containerOrSelector)
container.innerHTML = ''
mount(container)
}
return app
}
我们看看打印的数据是什么样的
看看ensureRenderer().createApp
做了哪些操作。
function ensureRenderer() {
return renderer || (renderer = createRenderer(rendererOptions))
}
function createRenderer(options) {
return baseCreateRenderer(options)
}
看样子 baseCreateRenderer
才是真容。
baseCreateRenderer
function baseCreateRenderer(options, createHydrationFns) {
// 下面全是封装的dom操作方法
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
// vue的diff过程叫做patch过程,这个方法是核心,下面会讲
const patch = ( n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, optimized = false) => {};
// 处理文本
const processText = (n1, n2, container, anchor) => {}
// 处理element
const processElement = ( n1, n2, container, ... ) => {}
// 下面的方法实在是太多了,就没有写入参
// 处理 fragment
const processFragment = () => {}
// 处理组件
const processComponent = () => {}
// 挂载element
const mountElement = () => {}
// 挂载组件
const mountComponent = () => {}
// 更新组件
const updateComponent = () => {}
// 挂载子节点
const mountChildren = () => {}
// 更新element
const patchElement = () => {}
// 更新子节点
const patchChildren = () => {}
// 运行带有副作用的render函数
const setupRenderEffect = () => {}
// diff,数组子节点发生变更,主要是,更新、删除、添加、移动几种方式处理
const patchKeyedChildren = () => {}
// 移动或插入子节点
const move = () => {}
// 卸载
const unmount = () => {}
// 卸载所有子组件
const unmountComponent = () => {}
// 卸载所有子节点
const unmountChildren = () => {}
// 渲染和挂载的流程
const render = (vnode, container) => {
// 没有vnode,卸载
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 创建或更新组件
patch(container._vnode || null, vnode, container)
}
// 缓存vnode
container._vnode = vnode
}
return {
render,
createApp: createAppAPI(render, hydrate),
}
}
因该方法里面太多处理了,就没有全部列出来,后续再慢慢更新!
继续往下看createAppAPI
createAppAPI
function createAppAPI(render){
return function createApp(rootComponent, rootProps = null) {
// rootComponent 就是上面打印的 'createApp.args>>>',也就是options
const context = createAppContext()
const app = (context.app = {
_uid: uid$1++,
_component: rootComponent,
_props: rootProps,
_container: null,
_context: context,
get config() {
return context.config
},
set config(v) {},
// 这里加载插件,和vue2不同的是,vue2的插件是全局的,这里只针对一个vue实例
use(plugin, ...options) {},
// 混入
mixin(mixin){},
// 加载组件
component(mixin){},
// 指令
directive(name, directive){},
// 挂载,核心渲染逻辑
mount(rootContainer, isHydrate){},
// 卸载
unmount(){},
// 注入
provide(){}
})
}
}
上面代码中的 mount
就是我们这篇的重点:
继续看代码:
// 挂载,核心渲染逻辑
mount = (rootContainer, isHydrate) => {
// 判断是否已挂载
if (!isMounted) {
// 创建虚拟节点
const vnode = createVNode(rootComponent, rootProps)
// 在根VNode上存储应用程序context
vnode.appContext = context
// 将虚拟节点渲染成真实dom
render(vnode, rootContainer)
isMounted = true
app._container = rootContainer
rootContainer.__vue_app__ = app
return vnode.component.proxy
} else {
}
}
上面的 render
就是 baseCreateRenderer
方法中的 render
。
总结调用流程:
createApp -> ensureRenderer -> baseCreateRenderer -> createAppAPI -> createAppContext -> mount -> render -> patch
下篇我们讲 patch。