vue渲染函數(shù)render的使用示例詳解
1. 前言
Vue 推薦在絕大多數(shù)情況下使用模板來(lái)創(chuàng)建你的 HTML。然而在一些場(chǎng)景中,你真的需要 JavaScript 的完全編程的能力。這時(shí)你可以用渲染函數(shù) render,它比模板更接近編譯器,直接生成 VNode 虛擬 DOM。
下面是一個(gè)對(duì)比例子,通過(guò) level prop 來(lái)動(dòng)態(tài)生成標(biāo)題的組件
- template 寫法
<template>
<h1 :class="[`title${level}`]" v-if="level === 1">
<slot></slot>
</h1>
<h2 :class="[`title${level}`]" v-else-if="level === 2">
<slot></slot>
</h2>
<h3 :class="[`title${level}`]" v-else-if="level === 3">
<slot></slot>
</h3>
<h4 :class="[`title${level}`]" v-else-if="level === 4">
<slot></slot>
</h4>
<h5 :class="[`title${level}`]" v-else-if="level === 5">
<slot></slot>
</h5>
<h6 :class="[`title${level}`]" v-else-if="level === 6">
<slot></slot>
</h6>
</template>
<script>
export default {
name: 'Title',
props: {
level: {
type: Number,
required: true,
},
},
}
</script>- render 寫法
export default {
name: 'Title',
props: {
level: {
type: Number,
required: true,
},
},
render(createElement) {
const children = this.$slots.default || [];
return createElement(`h${this.level}`, { class: [`title${this.level}`] }, children)
},
}2. 參數(shù)和語(yǔ)法
當(dāng)使用 render 函數(shù)描述虛擬 DOM 時(shí),vue 提供一個(gè)構(gòu)建虛擬 DOM 的函數(shù),叫 createElement,約定的簡(jiǎn)寫為 h。
2-1. 參數(shù)
createElement 函數(shù)有三個(gè)參數(shù):
- 必填。一個(gè) HTML 標(biāo)簽名、組件名,類型:{String | Object | Function}(也可以是組件選項(xiàng)對(duì)象或返回組件選項(xiàng)的函數(shù))
- 可選。一個(gè)與模板中屬性對(duì)應(yīng)的數(shù)據(jù)對(duì)象,也就是與模板中屬性對(duì)應(yīng)的數(shù)據(jù)對(duì)象,包含組件屬性、DOM 屬性、事件等。
- 可選。子級(jí)虛擬節(jié)點(diǎn) (VNodes),由
createElement()構(gòu)建而成,也可以使用字符串來(lái)生成"文本虛擬"節(jié)點(diǎn)。
語(yǔ)法:
createElement(TagName,Option,Content)
第一個(gè)參數(shù)也可以是組件選項(xiàng)對(duì)象或返回組件選項(xiàng)的函數(shù),例如:
// :
createElement({
template: '<div>{{ msg }}</div>',
data() {
return { msg: 'Hello' };
}
});2-2. Option數(shù)據(jù)對(duì)象
createElement 函數(shù)的第二個(gè)參數(shù),是一個(gè)與模板中屬性對(duì)應(yīng)的數(shù)據(jù)對(duì)象,也就是組件的屬性。
{
// 與 `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 attribute
attrs: {
id: 'foo'
},
// 組件 prop
props: {
myProp: 'bar'
},
// DOM property
domProps: {
innerHTML: 'baz'
},
// 事件監(jiān)聽器在 `on` 內(nèi),但不再支持如 `v-on:keyup.enter` 這樣的修飾器。需要在處理函數(shù)中手動(dòng)檢查 keyCode。
on: {
click: this.clickHandler
},
// 僅用于組件,用于監(jiān)聽原生事件,而不是組件內(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)
},
// 如果組件是其它組件的子組件,需為插槽指定名稱
slot: 'name-of-slot',
// 其它特殊頂層 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函數(shù)中給多個(gè)元素都應(yīng)用了相同的 ref 名,那么 `$refs.myRef` 會(huì)變成一個(gè)數(shù)組。
refInFor: true
}下面是一個(gè) Button 按鈕的例子:
export default {
name: 'Button',
props: {
text: {
type: String,
required: true,
},
},
methods: {
handleClick() {
console.log('按鈕被點(diǎn)擊了!');
}
},
render(h) {
return h(
'div',
{
class: 'button-wrapper',
on: {
click: handleClick,
},
},
[h('span', { class: 'button-text' }, this.text)]
)
},
}2-3. 指令變化
指令的寫法發(fā)生了變化,常用的 v-if/else,還有 v-for,v-model,事件修飾符等都有變化。
2-3-1. v-if和else
- 模板寫法:
<ul v-if="items.length">
<li v-for="item in items" :key="item">{{ item }}</li>
</ul>
<p v-else>空空如也</p>- render 函數(shù)寫法:
export default {
// 省略......
render(h) {
if (this.items.length) {
return h('ul', this.items.map((item) => h('li', { key: item }, item)));
} else {
return h('p', '空空如也');
}
}
}2-3-2. v-model
render 函數(shù)中沒有與 v-model 的直接對(duì)應(yīng),需要自己實(shí)現(xiàn)相應(yīng)的邏輯。
- 模板寫法
<input v-model="message" placeholder="請(qǐng)輸入" />
- render 函數(shù)寫法
export default {
// 省略......
props: ['message'],
render(h) {
const self = this
return h('input', {
domProps: {
value: self.message,
},
on: {
input: (event) => {
// 組件綁定使用了sync語(yǔ)法糖
self.$emit('update:message', event.target.value)
},
},
})
},
}2-3-3. 事件按鍵修飾符
對(duì)于事件修飾符,vue 官方提供了部分的特殊前綴來(lái)處理,其余的,則需要自己在函數(shù)中處理。
| 修飾符 | 前綴 | 說(shuō)明 |
|---|---|---|
| .passive | & | 滾動(dòng)事件的默認(rèn)行為將會(huì)立即觸發(fā),而不是等到事件觸發(fā)完再觸發(fā) |
| .capture | ! | 捕獲模式 |
| .once | ~ | 只觸發(fā)一次回調(diào) |
| .capture.once | ~! | 只觸發(fā)一次回調(diào) 的 捕獲模式 |
例子如下:
on: {
'!click': this.func,
'~keyup': this.func,
'~!mouseover': this.func,
keyup: (event) => {
// 如果觸發(fā)事件的元素不是事件綁定的元素
if (event.target !== event.currentTarget) return
// 如果按下去的不是 enter 鍵或者沒有同時(shí)按下 shift 鍵
f (!event.shiftKey || event.keyCode !== 13) return
// 阻止 事件冒泡
event.stopPropagation()
// 阻止該元素默認(rèn)的 keyup 事件
event.preventDefault()
}
}| 修飾符 | 操作 | 說(shuō)明 |
|---|---|---|
| .stop | event.stopPropagation() | 阻止冒泡 |
| .prevent | event.preventDefault() | 阻止元素發(fā)生默認(rèn)的行為 |
| .self | if (event.target !== event.currentTarget) return | 自身觸發(fā) |
| .enter | if (event.keyCode !== 13) return | 按鍵匹配 |
| .shift | if (!event.shiftKey) return | 按鍵匹配 |
2-3-4. 插槽
可以通過(guò) this.$slots 訪問靜態(tài)插槽的內(nèi)容,每個(gè)插槽都是一個(gè) VNode 數(shù)組:
render: function (h) {
return h('div', this.$slots.default)
}也可以通過(guò) this.$scopedSlots 訪問作用域插槽,每個(gè)作用域插槽都是一個(gè)返回若干 VNode 的函數(shù):
props: ['message'],
render: function (h) {
return h('div', [
this.$scopedSlots.default({
text: this.message
})
])
}如果要用渲染函數(shù)向子組件中傳遞作用域插槽,可以利用 VNode 數(shù)據(jù)對(duì)象中的 scopedSlots 字段:
render: function (h) {
return h('div', [
h('child', {
// 在數(shù)據(jù)對(duì)象中傳遞 `scopedSlots` 格式為 { name: props => VNode | Array<VNode> }
scopedSlots: {
default: function (props) {
return h('span', props.text)
}
}
})
])
}例子如下:
- list.js
const handleClick = (index) => {
return () => {
console.log(`${index}按鈕被點(diǎn)擊了!`)
}
}
export default {
name: 'List',
props: {
data: {
type: Array,
required: true,
default: () => [],
},
},
render(h) {
if (this.data.length > 0) {
return h(
'ul',
{
class: 'ul-box',
},
this.data.map((item, i) => {
return h(
'li',
{
class: 'li-box',
on: {
click: handleClick(i),
},
},
this.$scopedSlots.default({
data: `${item}+${i}`,
})
)
})
)
}
return h('div', '暫無(wú)數(shù)據(jù)')
},
}- home.vue
List<template>
<div class="home_box">
<List :data="list">
<template slot-scope="scope">
<span>{{ scope.data }}</span>
</template>
</List>
</div>
</template>
<script>
import List from '@/views/render/list.js'
export default {
name: 'Home',
components: { List },
data() {
return {
list: [100, 200, 300, 400, 500]
}
}
}
</script>3. 編譯jsx
如果你寫了很多 render 函數(shù),可能會(huì)覺得這樣的代碼寫起來(lái)很痛苦,并且可讀性也不好。這時(shí)候可以使用 JSX 插件:傳送門
- 使用JSX的render函數(shù)示例:
render() {
return (
<div class={`title${this.level}`}>
{this.$slots.default}
</div>
);
}4. 典型應(yīng)用場(chǎng)景
render 函數(shù)非常適合實(shí)現(xiàn)高階組件以及復(fù)雜的動(dòng)態(tài) UI,因?yàn)樗梢詣?dòng)態(tài)創(chuàng)建和組合組件。
4.1 高階組件示例
// 動(dòng)態(tài)加載組件
export default function asyncComponentLoader(componentName) {
return {
name: `AsyncLoader(${componentName})`,
data() {
return {
Component: null,
loading: true,
error: null,
};
},
async created() {
try {
const componentModule = await import(`@/components/${componentName}.vue`);
this.Component = componentModule.default || componentModule;
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
},
render(h) {
if (this.loading) return h('div', 'Loading...');
if (this.error) return h('div', { style: { color: 'red' } }, this.error);
if (this.Component) return h(this.Component, { props: this.$props });
return h('div', 'Component not found');
},
};
}4.2. 復(fù)雜的動(dòng)態(tài)UI示例
// 動(dòng)態(tài)表單生成器
export default {
name: 'DynamicForm',
props: {
formConfig: {
type: Array,
required: true,
},
formData: {
type: Object,
default: () => ({}),
},
},
methods: {
handleInput(field, event) {
this.$emit('input', { ...this.formData, [field]: event.target.value });
},
},
render(h) {
const formItems = this.formConfig.map((field) => {
switch (field.type) {
case 'text':
return h('div', [
h('label', field.label),
h('input', {
attrs: { type: 'text', placeholder: field.placeholder },
domProps: { value: this.formData[field.name] || '' },
on: { input: (e) => this.handleInput(field.name, e) },
}),
]);
case 'select':
return h('div', [
h('label', field.label),
h('select', {
domProps: { value: this.formData[field.name] || '' },
on: { input: (e) => this.handleInput(field.name, e) },
}, field.options.map(option =>
h('option', { attrs: { value: option.value } }, option.label)
)),
]);
case 'checkbox':
return h('div', [
h('label', [
h('input', {
attrs: { type: 'checkbox' },
domProps: { checked: this.formData[field.name] || false },
on: { input: (e) => this.handleInput(field.name, e) },
}),
field.label,
]),
]);
default:
return null;
}
});
return h('form', {
on: {
submit: (e) => {
e.preventDefault();
this.$emit('submit', this.formData);
},
}
}, [
...formItems,
h('button', { attrs: { type: 'submit' } }, '提交')
]);
},
};
// 使用示例
<DynamicForm
:form-config="formConfig"
:form-data="formData"
@input="formData = $event"
@submit="handleSubmit"
/>
// 配置示例
formConfig: [
{ type: 'text', name: 'username', label: '用戶名', placeholder: '請(qǐng)輸入用戶名' },
{ type: 'text', name: 'email', label: '郵箱', placeholder: '請(qǐng)輸入郵箱' },
{ type: 'select', name: 'role', label: '角色', options: [
{ value: 'admin', label: '管理員' },
{ value: 'user', label: '普通用戶' },
]},
{ type: 'checkbox', name: 'agreement', label: '我同意條款' },
],5. 性能
render 函數(shù)通常比 template 性能更高,原因如下:
- 避免了模板編譯過(guò)程
- 可以更精確地控制虛擬 DOM 的創(chuàng)建
- 在復(fù)雜動(dòng)態(tài) UI 場(chǎng)景中減少不必要的重新渲染
但請(qǐng)注意,不要為了性能而過(guò)度使用 render 函數(shù),保持代碼可讀性同樣重要。在大多數(shù)情況下,template 的性能已經(jīng)足夠好,沒必要為了什么原因全部使用render。
到此這篇關(guān)于vue渲染函數(shù)render的使用的文章就介紹到這了,更多相關(guān)vue渲染函數(shù)render內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用vue引入maptalks地圖及聚合效果的實(shí)現(xiàn)
這篇文章主要介紹了使用vue引入maptalks地圖及聚合效果的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Vue?+?SpringBoot?實(shí)現(xiàn)文件的斷點(diǎn)上傳、秒傳存儲(chǔ)到Minio的操作方法
這篇文章主要介紹了Vue?+?SpringBoot?實(shí)現(xiàn)文件的斷點(diǎn)上傳、秒傳存儲(chǔ)到Minio的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-06-06
vue實(shí)現(xiàn)移動(dòng)端可拖拽式icon圖標(biāo)
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)移動(dòng)端可拖拽式icon圖標(biāo),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
使用vue-router設(shè)置每個(gè)頁(yè)面的title方法
下面小編就為大家分享一篇使用vue-router設(shè)置每個(gè)頁(yè)面的title方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
Vue 中如何利用 new Date() 獲取當(dāng)前時(shí)間
在 Vue 開發(fā)中,利用 new Date() 方法可以方便地獲取當(dāng)前時(shí)間,并通過(guò) Date 對(duì)象的方法進(jìn)行時(shí)間格式化和操作。通過(guò)本文的介紹,您應(yīng)該對(duì)在 Vue 中獲取當(dāng)前時(shí)間有了更深入的了解,并了解了一些常見的時(shí)間操作方法,需要的朋友可以參考下2023-07-07
element plus tree拖動(dòng)節(jié)點(diǎn)交換位置和改變層級(jí)問題(解決方案)
圖層list里有各種組件,用element plus的tree來(lái)渲染,可以把圖片等組件到面板里,面板是容器,非容器組件,比如圖片、文本等,就不能讓其他組件拖進(jìn)來(lái),這篇文章主要介紹了element plus tree拖動(dòng)節(jié)點(diǎn)交換位置和改變層級(jí)問題(解決方案),需要的朋友可以參考下2024-04-04
使用vue-router切換頁(yè)面時(shí)實(shí)現(xiàn)設(shè)置過(guò)渡動(dòng)畫
今天小編就為大家分享一篇使用vue-router切換頁(yè)面時(shí)實(shí)現(xiàn)設(shè)置過(guò)渡動(dòng)畫。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10
vue如何利用axios調(diào)用后臺(tái)api接口
這篇文章主要介紹了vue如何利用axios調(diào)用后臺(tái)api接口問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07

