上篇文章我们了解vue3可以通过解构的方式引入createApp创建app实例,本来想直接分析源码,发现如果一开始不懂设计理念,实力又不足,直接看源码会被劝退的,已经被教育很多次了。
还是老老实实一步步来,这篇文章先来分析下createApp的简单版实现过程,后面再单独分析数据响应式(减轻学习负担)。
1. 基本结构
我们先看下使用方式,然后把这个功能当成需求来实现,通过逆向分析可能会更容易理解。
const {createApp} = Vue;
const app = createApp({
setup() {
return {
title: 'hi,vue3'
}
}
});
app.mount('#app');
通过vue3使用实例(也可以理解为测试用例)看到createApp能够解构引入,我们大概可以猜出reateApp是Vue对象的一个方法。
再看该方法支持参入对象,并且返回的app可以调用mount方法。可见这个函数会返回一个mount方法,同时mount方法支持传入选择器字符串。
由此可推断基本结构应该为:
const Vue = {
// options接收参数
createApp(options) {
return {
// 返回一个mount方法
mount(selector) {
// xxx
}
}
}
}
2. 页面渲染流程
createApp主要是返回一个应用程序实例app,页面渲染最终靠app.mount('#app')。mount方法是如何把传入的参数配置与dom相结合渲染页面的呢?
为了简化整个流程,我们先不考虑虚拟dom(其实无论是否有虚拟dom,最终页面渲染都会操作真实dom)。通过执行mount,传入了选择器会得到要渲染的根元素,在这之前需要找到要插入的元素。核心点如下:
2.1 获取渲染函数
主要目的是得到要渲染的dom元素,为了更好的扩展性,vue3.0支持自定义渲染函数,可通过options传入,如果不传会提供默认的渲染函数。继续完善基本结构:
const Vue = {
// options接收参数
createApp(options) {
return {
// 返回一个mount方法
mount(selector) {
// 获取根元素容器
let parent = document.querySelector(selector);
// 如果没有配置渲染函数,使用自定义的编译函数得到渲染函数
if (!options.render) {
options.render = this.comiple(parent.innerHTML);
}
},
// 编译模板得到渲染函数
comiple(template) {
return function render() {
// xxx
}
}
}
}
}
2.2 执行渲染函数
执行渲染函数将传入参数的配置和模板结合得到渲染的元素内容。继续完善基本结构:
const Vue = {
// options接收参数
createApp(options) {
return {
// 返回一个mount方法
mount(selector) {
// 获取根元素容器
let parent = document.querySelector(selector);
// 如果没有配置渲染函数,使用自定义的编译函数得到渲染函数
if (!options.render) {
options.render = this.comiple(parent.innerHTML);
}
// 通过call执行渲染函数,并将setup的返回值作为this
options.render.call(options.setup());
},
// 自定义渲染函数
comiple(template) {
// template暂时不解析
return function render() {
// 先不考虑虚拟dom,直接用真实dom代替
const div = document.createElement('div');
// 这里的this就是setup函数的返回值
div.innderHTML = this.title;
return div
}
}
}
}
}
2.3 追加到根元素
在追加前需要清空根元素内容,然后再将处理好的模板元素进行追加。
const Vue = {
// options接收参数
createApp(options) {
return {
// 返回一个mount方法
mount(selector) {
// 宿主元素
let parent = document.querySelector(selector);
// 如果没有配置渲染函数,使用自定义的编译函数得到渲染函数
if (!options.render) {
options.render = this.comiple(parent.innerHTML);
}
// 通过call执行渲染函数,并将setup的返回值作为this
const el = options.render.call(options.setup());
// 清空根元素内容
parent.innerHTML = '';
// 将el追加到根元素
parent.appendChild(el);
},
// 自定义渲染函数
comiple(template) {
// template暂时不处理
return function render() {
// 先不考虑虚拟dom,直接用真实dom代替
const div = document.createElement('div');
// 这里的this就是setup函数的返回值
div.innderHTML = this.title;
return div
}
}
}
}
}
把之前html引入的vue3去掉,换成我们自己写的,下面是个完整可运行的html文件,浏览器可以看到页面渲染出了hi,vue3
<html>
<body>
<div id="app">
<h3>{{title}}</h3>
</div>
<script>
const Vue = {
createApp(options) {
return {
mount(selector) {
const parent = document.querySelector(selector);
if (!options.render) {
// 如果没有配置渲染函数,使用自定义的编译函数得到渲染函数
options.render = this.compile(parent.innerHTML);
}
// 通过call执行函数,并将setup的返回值作为this
const el = options.render.call(options.setup());
// 清空宿主元素的内容
parent.innerHTML = '';
// 将el追加到宿主元素
parent.appendChild(el);
},
compile(template){
// template暂时不处理
return function render() {
const div = document.createElement('div');
// 这里的this就是setup函数的返回值
div.innerHTML = this.title;
return div
}
}
}
}
}
</script>
<script>
const {createApp} = Vue;
const app = createApp({
setup() {
return {
title: 'hi,vue3'
}
}
});
app.mount('#app');
</script>
</body>
</html>
3. 总结
上面步骤只是简易版的实现流程,可以帮助我们更好的理解整体流程和阅读源码。vue3源码其实做了很多工作,例如兼容vue2写法、虚拟dom节点处理、模板编译优化、渲染器和利用工厂函数来支持跨平台功能等等。路漫漫其修远兮...
感谢点赞支持~
附上webpack系列历史文章(比较干货):
【webpack系列】从源码角度分析webpack打包产出及核心流程
【webpack系列】从源码角度分析loader是如何触发和执行的
【webpack系列】webpack是如何解析模块的
【webpack系列】webpack的plugin插件是如何运行的
【webpack系列】从源码角度分析webpack热更新流程
【webpack系列】从源码角度深度剖析html-webpack-plugin执行过程