说明
本系列将以一个 demo 为基础,从源码执行流程的角度进行分析,涉及内容只包含 vue-next 新特性,如 proxy、setup 等,预计共分为七篇:
- 【真香系列】Vue-Next 源码第一章:阅读源码的准备工作
- 【真香系列】Vue-Next 源码第二章:初始化流程上
- 【真香系列】Vue-Next 源码第三章:初始化流程下
- 【真香系列】Vue-Next 源码第四章:更新
- 【真香系列】Vue-Next 源码第五章:reative & effect
- 【真香系列】Vue-Next 源码第六章:新特性原理
- 【真香系列】Vue-Next 源码第七章:实战组件
第四章完成后会提供一个整体运行流程的脑图供大家参考
准备工作
- 克隆代码并安装依赖
git clone https://github.com/vuejs/vue-next.git
cd vue-next
yarn
- 修改
rollup.config.js和tsconfig.json配置,开启sourcemap
function createConfig(format, output, plugins = []) {
if (!output) {
console.log(require('chalk').yellow(`invalid format: "${format}"`))
process.exit(1)
}
// output.sourcemap = !!process.env.SOURCE_MAP
output.sourcemap = true // 开启 sourcemap
...
}
{
"compilerOptions": {
"sourceMap": true,
}
...
}
- 编译
dev dist
yarn dev
- 在项目根目录创建
demo,包含demo.html和demo.js
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../packages/vue/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
</body>
<script src="./index.js"></script>
</html>
const { reactive } = Vue
const App = {
template: `<h1>{{state.message}}</h1>`,
setup() {
const state = reactive({ message: 'World' })
setTimeout(() => {
state.message = 1
}, 3000)
return {
state
}
}
}
const app = Vue.createApp(App)
app.mount('#app')
- 运行
demo
yarn serve
createApp
在 demo 中首先声明了一个 app 组件选项,然后通过 createApp 创建组件实例
// packages/runtime-dom/src/index.ts
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
if (__DEV__) {
injectNativeTagCheck(app)
}
const { mount } = app
app.mount = (containerOrSelector: Element | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// clear content before mounting
container.innerHTML = ''
const proxy = mount(container)
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}
return app
}) as CreateAppFunction<Element>
首先 ensureRenderer() 调用 createRenderer 方法创建了一个平台相关的渲染器,在本例中就是浏览器环境。参数 rendererOptions 包含了浏览器的一些 dom 操作方法,如 insert、 remove 等。渲染器主要包括两个方法 render 和 createApp。
// packages/runtime-dom/src/index.ts
function ensureRenderer() {
return renderer || (renderer = createRenderer<Node, Element>(rendererOptions))
}
// packages/runtime-core/src/renderer.ts
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
// packages/runtime-core/src/renderer.ts
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
ensureRenderer 之后调用了 createApp, 实际就是 createAppAPI 返回的函数,该函数返回了 Vue 实例,后续会使用实例的 mount 方法去挂载。
// packages/runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
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,
get config() {},
set config(v) {},
use(plugin: Plugin, ...options: any[]) {},
mixin(mixin: ComponentOptions) {},
component(name: string, component?: Component): any {},
directive(name: string, directive?: Directive) {},
mount(rootContainer: HostElement, isHydrate?: boolean): any {},
unmount() {},
provide(key, value) {}
})
return app
}
}
mount
例子中 app.mount('#app') 将组件挂载到了容器下,mount 其实是一个被包装过的方法。首先备份 app 上的 mount 方法,然后针对浏览器环境将其重写。
// packages/runtime-dom/src/index.ts
export const createApp = ((...args) => {
const app = ensureRenderer().createApp(...args)
const { mount } = app
app.mount = (containerOrSelector: Element | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
component.template = container.innerHTML
}
// clear content before mounting
container.innerHTML = ''
const proxy = mount(container)
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}
return app
}) as CreateAppFunction<Element>
总结
这部分主要介绍了 app 如何被创建出来以及针对 runtime 的一些额外操作。后面我们从 mount 为入口点,开始介绍挂载流程,其中 app 上的一些被定义的方法也会涉及到。