vue渲染函數(shù)render的使用示例詳解
1. 前言
Vue 推薦在絕大多數(shù)情況下使用模板來創(chuàng)建你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全編程的能力。這時你可以用渲染函數(shù) render,它比模板更接近編譯器,直接生成 VNode 虛擬 DOM。
下面是一個對比例子,通過 level prop 來動態(tài)生成標題的組件
- 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ù)和語法
當使用 render 函數(shù)描述虛擬 DOM 時,vue 提供一個構建虛擬 DOM 的函數(shù),叫 createElement,約定的簡寫為 h。
2-1. 參數(shù)
createElement 函數(shù)有三個參數(shù):
- 必填。一個 HTML 標簽名、組件名,類型:{String | Object | Function}(也可以是組件選項對象或返回組件選項的函數(shù))
- 可選。一個與模板中屬性對應的數(shù)據(jù)對象,也就是與模板中屬性對應的數(shù)據(jù)對象,包含組件屬性、DOM 屬性、事件等。
- 可選。子級虛擬節(jié)點 (VNodes),由
createElement()
構建而成,也可以使用字符串來生成"文本虛擬"節(jié)點。
語法:
createElement(TagName,Option,Content)
第一個參數(shù)也可以是組件選項對象或返回組件選項的函數(shù),例如:
// : createElement({ template: '<div>{{ msg }}</div>', data() { return { msg: 'Hello' }; } });
2-2. Option數(shù)據(jù)對象
createElement 函數(shù)的第二個參數(shù),是一個與模板中屬性對應的數(shù)據(jù)對象,也就是組件的屬性。
{ // 與 `v-bind:class` 的 API 相同,接受一個字符串、對象或字符串和對象組成的數(shù)組 'class': { foo: true, bar: false }, // 與 `v-bind:style` 的 API 相同,接受一個字符串、對象,或對象組成的數(shù)組 style: { color: 'red', fontSize: '14px' }, // 普通的 HTML attribute attrs: { id: 'foo' }, // 組件 prop props: { myProp: 'bar' }, // DOM property domProps: { innerHTML: 'baz' }, // 事件監(jiān)聽器在 `on` 內,但不再支持如 `v-on:keyup.enter` 這樣的修飾器。需要在處理函數(shù)中手動檢查 keyCode。 on: { click: this.clickHandler }, // 僅用于組件,用于監(jiān)聽原生事件,而不是組件內部使用`vm.$emit` 觸發(fā)的事件。 nativeOn: { click: this.nativeClickHandler }, // 自定義指令。注意,你無法對 `binding` 中的 `oldValue`賦值,因為 Vue 已經自動為你進行了同步。 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ù)中給多個元素都應用了相同的 ref 名,那么 `$refs.myRef` 會變成一個數(shù)組。 refInFor: true }
下面是一個 Button 按鈕的例子:
export default { name: 'Button', props: { text: { type: String, required: true, }, }, methods: { handleClick() { console.log('按鈕被點擊了!'); } }, 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 的直接對應,需要自己實現(xiàn)相應的邏輯。
- 模板寫法
<input v-model="message" placeholder="請輸入" />
- render 函數(shù)寫法
export default { // 省略...... props: ['message'], render(h) { const self = this return h('input', { domProps: { value: self.message, }, on: { input: (event) => { // 組件綁定使用了sync語法糖 self.$emit('update:message', event.target.value) }, }, }) }, }
2-3-3. 事件按鍵修飾符
對于事件修飾符,vue 官方提供了部分的特殊前綴來處理,其余的,則需要自己在函數(shù)中處理。
修飾符 | 前綴 | 說明 |
---|---|---|
.passive | & | 滾動事件的默認行為將會立即觸發(fā),而不是等到事件觸發(fā)完再觸發(fā) |
.capture | ! | 捕獲模式 |
.once | ~ | 只觸發(fā)一次回調 |
.capture.once | ~! | 只觸發(fā)一次回調 的 捕獲模式 |
例子如下:
on: { '!click': this.func, '~keyup': this.func, '~!mouseover': this.func, keyup: (event) => { // 如果觸發(fā)事件的元素不是事件綁定的元素 if (event.target !== event.currentTarget) return // 如果按下去的不是 enter 鍵或者沒有同時按下 shift 鍵 f (!event.shiftKey || event.keyCode !== 13) return // 阻止 事件冒泡 event.stopPropagation() // 阻止該元素默認的 keyup 事件 event.preventDefault() } }
修飾符 | 操作 | 說明 |
---|---|---|
.stop | event.stopPropagation() | 阻止冒泡 |
.prevent | event.preventDefault() | 阻止元素發(fā)生默認的行為 |
.self | if (event.target !== event.currentTarget) return | 自身觸發(fā) |
.enter | if (event.keyCode !== 13) return | 按鍵匹配 |
.shift | if (!event.shiftKey) return | 按鍵匹配 |
2-3-4. 插槽
可以通過 this.$slots 訪問靜態(tài)插槽的內容,每個插槽都是一個 VNode 數(shù)組:
render: function (h) { return h('div', this.$slots.default) }
也可以通過 this.$scopedSlots 訪問作用域插槽,每個作用域插槽都是一個返回若干 VNode 的函數(shù):
props: ['message'], render: function (h) { return h('div', [ this.$scopedSlots.default({ text: this.message }) ]) }
如果要用渲染函數(shù)向子組件中傳遞作用域插槽,可以利用 VNode 數(shù)據(jù)對象中的 scopedSlots 字段:
render: function (h) { return h('div', [ h('child', { // 在數(shù)據(jù)對象中傳遞 `scopedSlots` 格式為 { name: props => VNode | Array<VNode> } scopedSlots: { default: function (props) { return h('span', props.text) } } }) ]) }
例子如下:
- list.js
const handleClick = (index) => { return () => { console.log(`${index}按鈕被點擊了!`) } } 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', '暫無數(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ù),可能會覺得這樣的代碼寫起來很痛苦,并且可讀性也不好。這時候可以使用 JSX 插件:傳送門
- 使用JSX的render函數(shù)示例:
render() { return ( <div class={`title${this.level}`}> {this.$slots.default} </div> ); }
4. 典型應用場景
render 函數(shù)非常適合實現(xiàn)高階組件以及復雜的動態(tài) UI,因為它可以動態(tài)創(chuàng)建和組合組件。
4.1 高階組件示例
// 動態(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. 復雜的動態(tài)UI示例
// 動態(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: '請輸入用戶名' }, { type: 'text', name: 'email', label: '郵箱', placeholder: '請輸入郵箱' }, { type: 'select', name: 'role', label: '角色', options: [ { value: 'admin', label: '管理員' }, { value: 'user', label: '普通用戶' }, ]}, { type: 'checkbox', name: 'agreement', label: '我同意條款' }, ],
5. 性能
render 函數(shù)通常比 template 性能更高,原因如下:
- 避免了模板編譯過程
- 可以更精確地控制虛擬 DOM 的創(chuàng)建
- 在復雜動態(tài) UI 場景中減少不必要的重新渲染
但請注意,不要為了性能而過度使用 render 函數(shù),保持代碼可讀性同樣重要。在大多數(shù)情況下,template 的性能已經足夠好,沒必要為了什么原因全部使用render。
到此這篇關于vue渲染函數(shù)render的使用的文章就介紹到這了,更多相關vue渲染函數(shù)render內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用vue引入maptalks地圖及聚合效果的實現(xiàn)
這篇文章主要介紹了使用vue引入maptalks地圖及聚合效果的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08Vue?+?SpringBoot?實現(xiàn)文件的斷點上傳、秒傳存儲到Minio的操作方法
這篇文章主要介紹了Vue?+?SpringBoot?實現(xiàn)文件的斷點上傳、秒傳存儲到Minio的操作方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-06-06element plus tree拖動節(jié)點交換位置和改變層級問題(解決方案)
圖層list里有各種組件,用element plus的tree來渲染,可以把圖片等組件到面板里,面板是容器,非容器組件,比如圖片、文本等,就不能讓其他組件拖進來,這篇文章主要介紹了element plus tree拖動節(jié)點交換位置和改變層級問題(解決方案),需要的朋友可以參考下2024-04-04使用vue-router切換頁面時實現(xiàn)設置過渡動畫
今天小編就為大家分享一篇使用vue-router切換頁面時實現(xiàn)設置過渡動畫。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-10-10