前言
前面一章我們了解了,開發(fā)體驗(yàn)是衡量一個(gè)框架的重要指標(biāo)之一。提供友好的警告信息至關(guān)重要,但是越詳細(xì)的警告信息,意味著框架體積越大。為了解決這一問(wèn)題,可以利用 Tree-Shaking 機(jī)制,配合構(gòu)建工具預(yù)定義常量,例如 __DEV __ ,從而實(shí)現(xiàn)只在開發(fā)環(huán)境中打印警告信息,生產(chǎn)環(huán)境中清楚這些代碼,達(dá)到代碼體積的可控性。
Tree-Shaking 是一種清除 dead code 的機(jī)制。可以利用/* #PURE */ 來(lái)輔助構(gòu)建工具進(jìn)行 Tree-Shaking。
框架的錯(cuò)誤處理做的好壞決定了用戶程序的健壯性,因此需要為用戶提供統(tǒng)一的錯(cuò)誤處理接口,這樣用戶可以通過(guò)注冊(cè)自定義的錯(cuò)誤處理函數(shù)來(lái)處理異常。
1、聲明式描述UI
Vue.js 模板描述
<h1 @click="handleClick">
<span></span>
</h1>
js 對(duì)象描述上面的UI
const title = {
tag: 'h1',
props: {
onClick: handleClick
},
children: [
{
tag: 'span'
}
]
}
如果我們想表示一個(gè)標(biāo)題,根據(jù)標(biāo)題級(jí)別的不同,分別采用h1~ h6 這幾個(gè)標(biāo)簽,如果用js實(shí)現(xiàn)
let level = 1;
const title = {
tag: `h${level}`
}
如果 level 的值變化,相應(yīng)的標(biāo)簽名稱也會(huì)變化。但是如果用 模板來(lái)描述,就需要一個(gè)個(gè)列舉出來(lái)。
<h1 v-if="level === 1"></h1>
<h2 v-else-if="level === 2"></h2>
...
<h6 v-else-if="level === 6"></h6>
遠(yuǎn)遠(yuǎn)沒有js對(duì)象靈活。使用js對(duì)象來(lái)描述 UI 的方式,其實(shí)就是虛擬DOM。Vue3中除了支持使用模板描述 UI外,還支持虛擬DOM描述 UI。例如我們手寫的渲染函數(shù)就是使用虛擬 DOM 描述UI。
import {h} from 'vue';
export default {
render() {
return h('h1', {
onClick: handleClick
})
}
}
2、渲染器
虛擬 DOM 就是用 JS 對(duì)象來(lái)描述真實(shí)的DOM結(jié)構(gòu)。如何將虛擬DOM變成真實(shí)的DOM并且渲染到瀏覽器頁(yè)面中---渲染器
渲染器的作用,把虛擬DOM渲染為真實(shí)DOM
虛擬DOM如下:
const vnode = {
tag: 'div',
props: {
onClick: () => alert('hello')
},
children: 'click me'
}
編寫一個(gè) 渲染器,將上面的虛擬 DOM 渲染成真實(shí)DOM
function renderer (vnode, container) {
// 創(chuàng)建 dom 元素
const el = document.createElement(vnode.tag);
// 添加屬性、事件到 dom 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// on 開頭,事件,給元素綁定事件
el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
}
}
// children 處理
if (typeof vnode.children === 'string') {
// string -- 文本
el.appendChild(document.createTextNode(vnode.children));
} else if (Array.isArray(vnode.children)) {
// 數(shù)組,遞歸調(diào)用,渲染子節(jié)點(diǎn)
vnode.children.forEach(child => renderer(child, el));
}
container.appendChild(el)
}
運(yùn)行結(jié)果:
上述分析器實(shí)現(xiàn)思路
- 創(chuàng)建DOM元素,vnode.tag 為標(biāo)簽名
- 給元素添加屬性和事件。遍歷 props 對(duì)象,如果 key 是 on 開頭,說(shuō)明是事件,截取字符 on 使用 toLowerCase將事件名稱小寫,再調(diào)用 addEventListener 綁定事件
- 處理元素的 children。如果是字符串,創(chuàng)建文本節(jié)點(diǎn),如果是數(shù)組,遞歸調(diào)用 renderer 函數(shù)渲染。
發(fā)現(xiàn)渲染器實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,主要是使用一些熟悉的 DOM 操作 API 來(lái)完成渲染工作。上面只是創(chuàng)建節(jié)點(diǎn),渲染器的精髓在更新節(jié)點(diǎn)的階段,需要精確找啊到 vnode對(duì)象的變更點(diǎn),并且只更新變更的內(nèi)容。
3、組件
上面了解了渲染器會(huì)將虛擬 DOM 渲染成真實(shí)的 DOM 元素。那組件呢,組件和虛擬 DOM 之前有什么關(guān)系。
**組件是一組 DOM 元素的封裝,這組 DOM 元素就是組件要渲染的內(nèi)容。**定義一個(gè)函數(shù)來(lái)代表組件,函數(shù)的返回值代表組件要渲染的內(nèi)容。
const MyComponent = function () {
return {
tag: 'div',
props: {
onClick: () => alert('click')
},
children: 'click me'
}
}
可以看到組件的 返回值也是虛擬 DOM,代表了組件要渲染的內(nèi)容,那么我們就可以使用下面的方式來(lái)描述組件。其中 tag 可以用來(lái)描述組件。
const vnode = {
tag: MyComponent
}
修改之前的渲染函數(shù)
// 渲染函數(shù)
renderer(vnode, container) {
if (typeof vnode.tag === 'string') {
// string -- 普通標(biāo)簽元素
this.mountElement(vnode, container);
} else if (typeof vnode.tag === 'function') {
// string -- 組件
this.mountComponent(vnode, container);
}
},
// 渲染標(biāo)簽元素
mountElement(vnode, container) {
// 創(chuàng)建 dom 元素
const el = document.createElement(vnode.tag);
// 添加屬性、事件到 dom 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// on 開頭,事件,給元素綁定事件
el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])
}
}
// children 處理
if (typeof vnode.children === 'string') {
// string -- 文本
el.appendChild(document.createTextNode(vnode.children));
} else if (Array.isArray(vnode.children)) {
// 數(shù)組,遞歸調(diào)用,渲染子節(jié)點(diǎn)
vnode.children.forEach(child => this.renderer(child, el));
}
container.appendChild(el)
},
// 渲染組件
mountComponent(vnode, container) {
// 調(diào)用組件函數(shù),獲取要渲染的內(nèi)容
const subTree = vnode.tag();
this.renderer(subTree, container)
},
4、模板的工作原理
上面我們了解了虛擬 DOM 是如何渲染成真實(shí) DOM的,那么模板是如何工作的呢?其實(shí)是依賴于編譯器。
編譯器的作用是將模板編譯成渲染函數(shù)。
模板如下
<div @click="handleClick">click me</div>
對(duì)編譯器來(lái)說(shuō),模板相當(dāng)于一個(gè)字符串,它會(huì)分析字符串并生成一個(gè)功能與之相同的渲染函數(shù):
render() {
return h('div', {onClick: handleClick}, 'click me')
}
在 Vue中一個(gè).vue文件就是一個(gè)組件
<template>
<div @click="handleClick">
click me
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
handleClick() {
// do nothing
}
}
}
</script>
其中 < template > 標(biāo)簽里的內(nèi)容為模板內(nèi)容,編譯器會(huì)把模板內(nèi)容編譯成渲染函數(shù)并且添加到 script 標(biāo)簽上,最終在瀏覽器里面運(yùn)行的代碼就是
export default {
data() {
return {
}
},
methods: {
handleClick() {
// do nothing
}
},
render() {
return h('div', {onClick: handleClick}, 'click me')
}
}
對(duì)一個(gè)組件來(lái)說(shuō),他要渲染的內(nèi)容最終都是通過(guò)渲染函數(shù)產(chǎn)生的,渲染器再將渲染函數(shù)返回的虛擬 DOM 渲染為真實(shí) DOM,這就是模板的工作原理,也是 Vue.js 渲染頁(yè)面的過(guò)程。
5、Vue.js 是各個(gè)模塊組成的有機(jī)整體
組件的實(shí)現(xiàn)依賴于渲染器,模板的編譯依賴于編譯器。編譯后生成的代碼是根據(jù)渲染器和虛擬 DOM的設(shè)計(jì)決定的。 因此Vue的各個(gè)模塊之間是相互關(guān)聯(lián),相互制約的。
模板
<div id="foo" :class="cls"></div>
渲染函數(shù)
render() {
return {
tag: 'div',
props: {
id: 'foo',
class: 'cls'
}
}
}
cls 是一個(gè)動(dòng)態(tài)屬性,那編譯器如何知道 cls 發(fā)生了變化呢。從上面的模板 我們可以看出 id 是不會(huì)發(fā)生變化的, :class 可能會(huì)發(fā)生變化。編譯器能識(shí)別出哪些是靜態(tài)屬性,哪些是動(dòng)態(tài)屬性,因此可以在生成代碼的時(shí)候,標(biāo)志出哪些是動(dòng)態(tài)的屬性。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-786968.html
render() {
return {
tag: 'div',
props: {
id: 'foo',
class: 'cls'
},
patchFlags: 1 // 1 代表 class是動(dòng)態(tài)的
}
}
在虛擬 DOM 中多了一個(gè) patchFlags 屬性標(biāo)志 class 的類型是動(dòng)態(tài)還是靜態(tài),這樣渲染器可以根據(jù)這個(gè)標(biāo)志,知道有什么屬性會(huì)發(fā)生變化。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-786968.html
到了這里,關(guān)于Vue.js設(shè)計(jì)與實(shí)現(xiàn)閱讀-3的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!