一、render函數(shù)是什么
簡(jiǎn)單的說(shuō),在vue中我們使用模板HTML語(yǔ)法來(lái)組建頁(yè)面的,使用render函數(shù)我們可以用js語(yǔ)言來(lái)構(gòu)建DOM。因?yàn)関ue是虛擬DOM,所以在拿到template模板時(shí)也要轉(zhuǎn)譯成VNode的函數(shù),而用render函數(shù)構(gòu)建DOM,vue就免去了轉(zhuǎn)譯的過(guò)程。
當(dāng)使用render函數(shù)描述虛擬DOM時(shí),vue提供一個(gè)函數(shù),這個(gè)函數(shù)是就構(gòu)建虛擬DOM所需要的工具。官網(wǎng)上給他起了個(gè)名字叫createElement。還有約定它的簡(jiǎn)寫(xiě)叫h
示例: 分別使用html語(yǔ)法和render函數(shù)來(lái)實(shí)現(xiàn)根據(jù)傳入的 level (h1-h6)頁(yè)面渲染不同的標(biāo)題格式
【1】使用組件的形式
// 父組件
<template>
<div>
<child1 :level='level'>我是標(biāo)題</child1>
</div>
</template>
<script>
const child1 = () => import("./child1.vue");
export default {
components: { child1 },
data() {
return {
level: 1
};
},
};
</script>
// 子組件 child1.vue
<template>
<div>
<h1 v-if="level == 1">
<slot></slot>
</h1>
<h2 v-if="level == 2">
<slot></slot>
</h2>
<h3 v-if="level == 3">
<slot></slot>
</h3>
<h4 v-if="level == 4">
<slot></slot>
</h4>
<h5 v-if="level == 5">
<slot></slot>
</h5>
<h6 v-if="level == 6">
<slot></slot>
</h6>
</div>
</template>
<script>
export default {
props: {
level: {
require: true,
type: Number,
}
}
};
</script>
【2】使用render函數(shù)的形式(修改child1子組件)
<script>
export default {
props: {
level: {
require: true,
type: Number,
}
},
render(createElement) {
return createElement('h' + this.level, this.$slots.default);
}
};
</script>
對(duì)比兩種實(shí)現(xiàn)方式,我們發(fā)現(xiàn)這里用模板并不是最好的選擇,不但代碼冗長(zhǎng),而且在每一個(gè)級(jí)別的標(biāo)題中重復(fù)書(shū)寫(xiě)了<slot></slot>。 使用render函數(shù)實(shí)現(xiàn)看起來(lái)簡(jiǎn)單多了,這樣代碼精簡(jiǎn)很多,但是需要非常熟悉 Vue 的實(shí)例屬性。在這個(gè)例子中,你需要知道,向組件中傳遞不帶v-slot指令的子節(jié)點(diǎn)時(shí),比如child1中的“我是標(biāo)題”,這些子節(jié)點(diǎn)被存儲(chǔ)在組件實(shí)例中的this.$slots.default中。
二、render函數(shù)的參數(shù)
render 函數(shù)即渲染函數(shù),它是個(gè)函數(shù),render 函數(shù)的返回值是VNode(即:虛擬節(jié)點(diǎn),也就是我們要渲染的節(jié)點(diǎn))
createElement 是 render 函數(shù)的參數(shù),它本身也是個(gè)函數(shù),并且有三個(gè)參數(shù)。接來(lái)下我們重點(diǎn)介紹這三個(gè)參數(shù)
【1】createElement 第一個(gè)參數(shù)是必填的,可以是String | Object | Function
- String,表示的是HTML 標(biāo)簽名
- Object ,一個(gè)含有數(shù)據(jù)的組件選項(xiàng)對(duì)象
- Function ,返回了一個(gè)含有標(biāo)簽名或者組件選項(xiàng)對(duì)象的async 函數(shù)
示例:
render: function (createElement) {
// String
return createElement('h1');
//Object
return createElement({
template: " <div>鋤禾日當(dāng)午</div> "
})
// Function
let domFun = function () {
return {
template: " <div> 鋤禾日當(dāng)午</div> "
}
}
return createElement(domFun())
}
【2】createElement 第二個(gè)參數(shù)是選填的,一個(gè)與模板中屬性對(duì)應(yīng)的數(shù)據(jù)對(duì)象 ****常用的有class | style | attrs | domProps | on
- class:控制類(lèi)名
- style :樣式
- attrs :用來(lái)寫(xiě)正常的 html 屬性 id src 等等
- domProps :用來(lái)寫(xiě)原生的dom 屬性
- on::用來(lái)寫(xiě)原生方法
return createElement('div', {
// 與 `v-bind:class` 的 API 相同,
// 接受一個(gè)字符串、對(duì)象或字符串和對(duì)象組成的數(shù)組
'class': {
foo: true,
bar: false
},
// 與 `v-bind:style` 的 API 相同,
// 接受一個(gè)字符串、對(duì)象,或?qū)ο蠼M成的數(shù)組
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 組件 prop
props: {
myProp: 'bar'
},
// DOM 屬性
domProps: {
innerHTML: 'baz'
},
// 事件監(jiān)聽(tīng)器在 `on` 屬性?xún)?nèi),
// 但不再支持如 `v-on:keyup.enter` 這樣的修飾器。
// 需要在處理函數(shù)中手動(dòng)檢查 keyCode。
on: {
click: this.clickHandler
},
// 僅用于組件,用于監(jiān)聽(tīng)原生事件,而不是組件內(nèi)部使用
// `vm.$emit` 觸發(fā)的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定義指令。注意,你無(wú)法對(duì) `binding` 中的 `oldValue`
// 賦值,因?yàn)?Vue 已經(jīng)自動(dòng)為你進(jìn)行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式為
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果組件是其它組件的子組件,需為插槽指定名稱(chēng)
slot: 'name-of-slot',
// 其它特殊頂層屬性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函數(shù)中給多個(gè)元素都應(yīng)用了相同的 ref 名,
// 那么 `$refs.myRef` 會(huì)變成一個(gè)數(shù)組。
refInFor: true
})
【3】createElement 第三個(gè)參數(shù)是選填的,代表子級(jí)虛擬節(jié)點(diǎn) (VNodes),由 createElement()
構(gòu)建而成,正常來(lái)講接收的是一個(gè)字符串或者一個(gè)數(shù)組,一般數(shù)組用的是比較多的
return createElement('div', {
attrs: {
id: "content"
}
}, [
createElement('h1', '我是H1標(biāo)題'),
createElement('h6', '我是H6標(biāo)題')
]
)
三、v--model在render函數(shù)中的使用
在render函數(shù)中,沒(méi)有提供v-model的實(shí)現(xiàn),所以你必須自己實(shí)現(xiàn)相應(yīng)的邏輯。這就是深入底層的代價(jià),但與v-model相比,這可以讓你更好地控制交互細(xì)節(jié)。
<template>
<div>
<child1 :name='name' v-model="name"></child1>
<p>{{name}}</p>
</div>
</template>
<script>
const child1 = () => import("./child1.vue");
export default {
components: { child1 },
data() {
return {
name: 'Demi'
};
},
};
</script>
<script>
export default {
props: {
name: {
require: true,
}
},
render(createElement) {
let self = this
return createElement('input', {
domProps: {
value: self.name
},
on: {
input(event) {
self.$emit('input', event.target.value)
}
}
})
}
};
</script>
結(jié)果如下:
render函數(shù)通過(guò)input方法實(shí)現(xiàn)數(shù)據(jù)雙向綁定,當(dāng)子組件name改變,父組件也隨著更新
四、render函數(shù)中的事件修飾符
對(duì)于.passive,.capture, .once 這些事件修飾符, Vue 提供了相應(yīng)的前綴可以用于 on
修飾符 | 前綴 |
---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once 或.once.capture
|
~! |
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover': this.doThisOnceInCapturingMode
}
五、JSX語(yǔ)法糖
JSX就是Javascript和XML結(jié)合的一種格式。React發(fā)明了JSX,利用HTML語(yǔ)法來(lái)創(chuàng)建虛擬DOM。當(dāng)遇到<,JSX就當(dāng)HTML解析,遇到{就當(dāng)JavaScript解析。
復(fù)雜的render函數(shù)書(shū)寫(xiě)異常痛苦,這就是為什么會(huì)有一個(gè)Babel插件,用于在 Vue 中使用 JSX 語(yǔ)法,它可以讓我們回到更接近于模板的語(yǔ)法上。
render(h) {
return (
<Child1 level={1}>
<span>Hello</span> world!
</Child1>
)
}
將h作為createElement的別名是 Vue 生態(tài)系統(tǒng)中的一個(gè)通用慣例,實(shí)際上也是 JSX 所要求的。從 Vue 的 Babel 插件的3.4.0版本開(kāi)始,我們會(huì)在以 ES2015 語(yǔ)法聲明的含有 JSX 的任何方法和 getter 中 (不是函數(shù)或箭頭函數(shù)中) 自動(dòng)注入const h = this.$createElement,這樣你就可以去掉(h)參數(shù)了。對(duì)于更早版本的插件,如果h在當(dāng)前作用域中不可用,應(yīng)用會(huì)拋錯(cuò)。
JSX語(yǔ)法學(xué)習(xí)文檔: vuejs/JSX JSX語(yǔ)法簡(jiǎn)介
文章每周持續(xù)更新,可以微信搜索「 前端大集錦 」第一時(shí)間閱讀,回復(fù)【視頻】【書(shū)籍】領(lǐng)取200G視頻資料和30本PDF書(shū)籍資料