Vue.js設計與實現之Vue.js3的設計思路
1.寫在前面
本文將立足于全局視角去了解Vue.js3的設計思路、工作機制以及一些重要的獨立組成部分,了解他們之間是如何相互獨立、又相互配合的。了解描述UI的兩種形式:模板字符串和虛擬DOM,Vue.js框架的兩個重要組成部分:編譯器和渲染器。
2.聲明式描述UI
通過前面的介紹,我們知道Vue.js3是聲明式UI框架,作者是如何參與設計一個聲明式的UI框架,編寫前端頁面又涉及到哪些內容?
- DOM元素
- 屬性
- 事件
- 元素的層級結構
上面這些內容是原生包含的,進行框架設計時如何讓它們變成響應式的,在Vue.js3中的解決方案是:
- 與原生html標簽一樣的描述DOM元素和屬性、層級結構等 <div id="app"><span>onechuan</span></div>
- 使用:或v-bind來描述動態綁定的屬性,使用@或v-on描述事件 <div id="app" :name="name" @click="handleClick">onechuan</div>
這樣,使用者不需要手寫任何命令式代碼,就可以實現聲明式描述UI。當然除了使用模板形式描述UI外,還能使用JS對象來描述:
const element = {
//標簽元素
tag: "div",
//標簽屬性
props:{
onClick: handleClick
},
//子節點
children: [
{ tag:"span" }
]
}對應的template模板是:
<div @click="handleClick"><span></span></div>
使用js數據對象描述和template模板有什么異同呢?
//h標簽的限制
const level = 3;
const element = {
tag: `h${level}`,//h3標簽
}
我們看到對于js對象結構代碼簡潔清晰,而使用模板則需要進行窮舉:
<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
<h3 v-else-if="level === 3"></h1>
<h4 v-else-if="level === 4"></h1>
<h5 v-else-if="level === 5"></h1>
<h6 v-else-if="level === 6"></h1>
我們在Vue.js組件中通過手寫渲染函數就是使用虛擬DOM來描述UI,h函數的返回值就是一個對象,用于開發者輕松地使用虛擬DOM描述UI。
import {h} from "vue";
export default {
render(){
return h("div",{ onClick: handleClick })//虛擬DOM
}
}對應的JS對象描述UI就是如下代碼段:
export default {
render(){
return {
tag:"div",
props:{onClick: handleClick }
}
}
}組件的渲染函數renderer的作用:Vue.js根據組件的render函數的返回值生成虛擬DOM,進而將組件內容進行渲染到頁面。
3.初識渲染器
我們可以使用JS數據對象來描述虛擬DOM,那么虛擬DOM又是如何通過渲染函數轉為真實DOM后,渲染到頁面的呢?

在前面的代碼片段中是使用JS數據對象來描述虛擬DOM:
const vnode = {
tag:"div",
props:{
onclick:()=>console.log("hello pingping")
},
children:"please click me"
}我們需要編寫渲染函數renderer去將虛擬DOM渲染為真實DOM,接收兩個參數:vnode虛擬DOM對象以及container真實DOM元素掛載點,container用于掛載渲染函數渲染的真實DOM元素。
function renderer(vnode, container){
// 使用vnode的tag作為標簽名渲染DOM元素
const el = document.createElement(vnode.tag)
// 遍歷vnode的props作為DOM元素的屬性和事件
for(const key in vnode.props){
if(/^on/.test(key)){
// 如果key以on開頭,這說明他是一個事件
el.addEventListener(
key.substring(2).toLowerCase(),//事件名稱將onClick-->click
vnode.props[key]//事件處理函數
)
}
}
// 處理children
if(typeof vnode.children === "string"){
// 如果是字符串,直接作為元素的文本子節點
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
// 遞歸調用renderer函數進行渲染子節點,使用當前元素el作為掛載點
vnode.children.forEach(child=>renderer(child,el))
}
// 將元素添加到掛載點
container.appendChild(el)
}調用:
renderer(vnode, document.body)
點擊按鈕后:

渲染器renderer的實現思路:
- 創建元素
- 給元素添加屬性和事件
- 遞歸遍歷children創建節點
4.組件的本質
組件是一組DOM元素的封裝,而這組DOM元素就是組件渲染的內容,對此可以定義函數來描述這段真實DOM元素對應的虛擬DOM對象。
const MyComponent = function(){
return {
tag:"div",
props:{
onClick: ()=>console.log("hello world")
},
children:"click me"
}
}通過虛擬DOM對象中tag屬性可以用于存儲組件函數,將其作為標簽類型進行渲染,同樣的也需要渲染器renderer函數。當然,此時的渲染函數需要根據待渲染標簽的類型進行判定,是普通標簽元素還是組件元素。
const vnode = {
tag: MyComponent
}
renderer(vnode, document.body)
function renderer(vnode, container){
// 判斷是標簽元素還是組件
if(typeof vnode.tag === "string"){
// 如果是字符串,直接作為元素的標簽元素
mountElement(vnode, container)
}else{
mountComponent(vnode, container)
}
}
// 渲染元素
function mountElement(vnode, container){
// 使用vnode.tag 作為標簽名稱創建DOM元素
const el = document.createElement(vnode.tag)
// 遍歷vnode的props作為DOM元素的屬性和事件
for(const key in vnode.props){
if(/^on/.test(key)){
// 如果key以on開頭,這說明他是一個事件
el.addEventListener(
key.substring(2).toLowerCase(),//事件名稱將onClick-->click
vnode.props[key]//事件處理函數
)
}
}
// 處理children
if(typeof vnode.children === "string"){
// 如果是字符串,直接作為元素的文本子節點
el.appendChild(document.createTextNode(vnode.children))
}else if(Array.isArray(vnode.children)){
// 遞歸調用renderer函數進行渲染子節點,使用當前元素el作為掛載點
vnode.children.forEach(child=>renderer(child,el))
}
// 將元素添加到掛載點
container.appendChild(el)
}
// 渲染組件
function mountComponent(vnode, container){
// 調用組件函數,獲取組件要渲染的內容-虛擬DOM
const subtree = vnode.tag()
// 遞歸調用renderer渲染subtree
renderer(subtree, container)
}
5.模板的工作原理
其實,手寫虛擬DOM還是使用模板形式,都可以聲明式描述UI,在Vue.js框架設計中是始終支持兩種形式的。我們知道JS對象描述虛擬DOM的形式,是通過渲染器renderer將虛擬DOM轉為真實DOM的,那么模板又是如何渲染到頁面的呢?
<div @click="handleClick">
hello pingping
</div>
模板字符串渲染到頁面,依賴于
模板字符串渲染到頁面,依賴于Vue.js框架的一個重要函數--編譯器compiler,其作用就是將模板字符串編譯成與之功能對應的渲染函數:
render(){
return h("div",{onClick: handleClick},"hello pingping")
}對此,我們知道無論是使用模板字符串還是直接手寫渲染函數,都是通過渲染函數將虛擬DOM轉為真實DOM,最終將描述的內容渲染到頁面上。只不過使用模板字符串多了一個編譯過程,需要通過編譯器將模板字符串編譯成渲染函數。
6.編譯器和渲染器組成有機整體
在Vue.js框架設計中,組件實現依賴于渲染器和編譯器,而渲染器和編譯器之間是彼此獨立又相互依賴的重要部分。

7.寫在后面
本文中,我們了解到Vue.js框架是通過聲明式描述UI的,而描述UI又有兩種形式:模板字符串和手寫虛擬DOM的形式,虛擬DOM形式比模板字符串更加靈活,但是模板字符串比虛擬DOM形式更加直觀。
在Vue.js框架設計中有兩種重要的組成部分:編譯器和渲染器,編譯器就是將模板字符串編譯成渲染函數,而渲染器則是將虛擬DOM創建為真實DOM,最終完成頁面的渲染。


































