这是我参与更文挑战的第2天,活动详情查看: 更文挑战
挺早之前就和村长学习了vue3的初始化流程剖析,奈何时间总是太少,除了学习完的第二天自己敲了几遍,之后都没有再次复习、总结、归纳,还要外力引导才霸蛮总结一次,惭愧惭愧...
首先,测试用例来一发,最后自己手写的初始化流程能跑通就算OK
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3 初始化流程学习</title>
</head>
<body>
<div id="app">
{{title}}
</div>
<!--
注释官方的vue3文件,放上我们自己写的mini版vue3文件
<script src="https://www.unpkg.com/vue@next"></script>
-->
<script src="./fvue3.js"></script>
<script>
const { createApp } = Vue
const app = createApp({
data () {
return {
title: 'hello vue3!'
}
},
setup () {
return {
title: 'vue3 初始化流程学习!'
}
}
})
app.mount('#app')
</script>
</body>
</html>
紧接着对着测试用例去实现Vue对象
- 实现createApp方法,为了兼容不同平台,重构一下这个方法,在这个方法中调用一个createRender方法,然后在createRender方法中返回真正的createApp方法
- 在真正的createApp方法中实现mount方法、compile编译方法
- 在编译方法中返回真正的渲染函数,这里直接返回测试用例中的元素即可
- 在mount方法中处理数据,并将返回的dom添加到parent里面
- PS: 还有将数据进行响应式处理,数据变动后驱动视图进行更新,将模板转换为虚拟DOM,对虚拟DOM进行比对以便精准更新,将虚拟DOM转换为真实DOM等等一系列的后面再写吧~
// fvue3.js
const Vue = {
createApp (options) {
// 以下方法仅支持在游览器中使用
const querySelector = selector => {
// 查找元素
return document.querySelector(selector)
}
const insert = (child, parent, anchor) => {
// 如果有anchor元素不存在相当于appendChild
parent.insertBefore(child, anchor || null)
}
const clearContent = parent => {
// 清除内容
parent.innerHTML = ''
}
const createEle = tag => {
return document.createElement(tag)
}
const setContent = (el, content) => {
el.textContent = content
}
// console.log(this)
// 注意,这里的this是指向window,原因是在创建app实例的时候用的createApp是直接解构赋值的
// const app = Vue.createApp({...})这样this才会指向Vue对象
const render = Vue.createRender({
querySelector,
insert,
clearContent,
createEle,
setContent
})
// 将createApp进行一层封装,以便兼容多平台
return render.createApp(options)
},
createRender ({ querySelector, insert, clearContent, createEle, setContent}) {
// 返回自定义渲染器
return {
createApp (options) {
// 返回app实例
return {
mount (selector) {
// mount函数,将vue实例挂载到某个元素上
const parent = querySelector(selector);
if (!options.render) {
// createApp选项是否有传入render函数,没有就执行compile函数中的render方法
// 将被挂载元素中的所有内容传入compile函数中
options.render = this.compile(parent.innerHTML)
}
// 因为vue3兼容vue2中data()的写法,所以判断下要从setup中获取数据还是从data中获取数据
// setup这种options api优先data
if (options.setup) {
this.setupData = options.setup()
} else {
this.data = options.data()
}
// 对实例做代理, 确定render中数据从哪获取
const proxy = new Proxy(this, {
get (target, key) {
if (key in target.setupData) {
return target.setupData[key]
} else {
return target.data[key]
}
},
set (target, key, value) {
if (key in target.setupData) {
target.setupData[key] = value
} else {
target.data[key] = value
}
}
})
const el = options.render.call(proxy)
// 追加到宿主元素上去
clearContent(parent)
insert(el, parent)
},
compile (template) {
// 简易版没有使用到template,只是实现将测试用例跑通
return function render () {
const h3 = createEle('h3')
setContent(h3, this.title)
return h3
}
}
}
}
}
}
}