使用vue框架的第一步就是引入createApp和App组件,那么在源码中createApp到底是怎么创建App组件实例的呢?
一.创建App应用实例
1. 框架中的使用
// main.js
import {createApp} from 'vue'
import App from './App.js'
const root = document.getElementById('root')
createApp(App).mount(root)
// App.js 本专栏均以这个app组件作为示例
export default {
template: `<p>{{msg}}</p>`,
setup() {
return {
msg: "Hello World",
};
},
};
2. createApp(源码)
源码createApp实际调用了ensureRenderer()函数返回值上的createApp方法
createApp() ---> ensureRenderer().createApp()
sequenceDiagram
createApp ->> createApp(App): 调用
createApp(App) ->> ensureRenderer().createApp(App): 返回
createApp ->> ensureRenderer().createApp(App): 等于
const createApp = (...args) => {
return ensureRenderer().createApp(...args);
};
3. ensureRenderer
源码定义了一个renderer作为渲染选项,同时ensureRenderer根据renderer是否存在判断是否通过createRenderer({})重新创建renderer。
sequenceDiagram
ensureRenderer().createApp(App) ->> ensureRenderer(): 调用
ensureRenderer() ->> ensureRenderer().createApp(App):返回renderer || createRenderer()
//定义了全局的渲染选项
let renderer;
function ensureRenderer() {
return (renderer ||
(renderer = createRenderer({
createElement,
createText,
setText,
setElementText,
patchProp,
insert,
remove,
})));
}
4. createRenderer()
调用时createRenderer()时传入了框架已经封装好的对于DOM的操作函数(不需要刻意关注)。
// 以createElement为例
function createElement(type) {
const element = document.createElement(type);
return element;
}
createRenderer使用解构赋值读取了传入的dom操作函数并进行重命名,之后返回了一个对象包括了一个render函数和createApp函数。
所以此时我们调用的createApp函数即为这里的createAppAPI(render)的返回值
sequenceDiagram
ensureRenderer().createApp(App) ->> ensureRenderer(): 调用
ensureRenderer() ->> createRenderer():调用
createRenderer() ->> createAppApi():调用
createRenderer() ->> ensureRenderer():返回
ensureRenderer() ->> ensureRenderer().createApp(App):返回{ renderer,createApp:createApi() }
ensureRenderer().createApp(App) ->> createAppApi():调用
function createRenderer(options) {
const { createElement: hostCreateElement, setElementText: hostSetElementText, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setText: hostSetText, createText: hostCreateText, } = options;
const render = (vnode, container) => {
patch(null, vnode, container);
};
return {
render,
createApp: createAppAPI(render),
};
}
5. createAppAPI
createAppAPI返回函数createApp(rootComponent),函数内部返回一个名为app的对象,对象包括一个_component内部属性指向调用createApp传入的rootComponent参数,在这里指向我们使用main.js创建app实例时传入的App组件,同时对象还包括一个mount方法,mount方法需要一个参数rootContainer指向我们调用mounted传入的名为root的Dom元素。同时mount内部调用了createVNode和传入的render --> patch()。
createApp() --> ensureRenderer().createApp() --> renderer.createApp() --> createRenderer().createApp() --> createAppAPI(render)() --> app
sequenceDiagram
ensureRenderer().createApp(App) ->> createAppApi() : ensureRenderer()等于 { renderer,createApp:createApi() }
createAppApi() ->> ensureRenderer().createApp(App):返回createApp函数
function createAppAPI(render) {
return function createApp(rootComponent) {
const app = {
_component: rootComponent,
mount(rootContainer) {
const vnode = createVNode(rootComponent);
render(vnode, rootContainer);
},
};
return app;
};
}
6. 总结
从createApp(App) --> 到最后实际调用了createAppAPI返回的createApp(rootComponent),
借用了JS的闭包(createApp 可以访问其外部作用域中的 render 函数,即使 createAppAPI 函数已经返回并且其外部作用域已经结束,也可以使用 render 函数),最后我们创建的应用实例实际为一个app对象,对象内部包含了一个内部属性_component指向传入的App组件,一个mount挂载函数,挂载函数内部调用了我们在createApp过程中传入的render函数(实际为patch函数)。同时全局属性renderer被赋值为一个包括render属性和createApp属性的对象。
sequenceDiagram
createApp(App) ->> ensureRenderer().createApp(App): 调用
ensureRenderer().createApp(App) ->> renderer.createApp(App): 调用
renderer.createApp(App) ->> createRenderer().createApp(App): 调用
createRenderer().createApp(App) ->> createAppAPI(render)(App): 调用
createAppAPI(render)(App) ->> app: 返回
createApp(App) ->> app:得到
const app = {
_component: rootComponent, //传入的App组件
mount(rootContainer){
const vnode = createVNode(rootComponent);
render(vnode, rootContainer);//patch()
}
}
let renderer;
renderer = {
render, //patch()
createApp:createAppAPI(render)
}