大多時候,我會使用template, vue單文件去渲染組件。雖然知道vue中有個render函數,但卻很少在項目中去主動使用它。使用最多的地方是在使用一些UI框架的時候,比如iview table中的按鈕操作,會使用到render函數。另外平時在閱讀一些Vue UI框架源碼的時候,也時常能遇到使用render函數的地方,這也激發了自己研究學習的欲望。如果你也感興趣,那就繼續閱讀吧。
在本文中,會有如下內容:
- 什么是Vue render函數
- Vue編譯器如何處理render函數
- 創建一個組件
- 在render函數中使用指令
- Vue渲染函數中的事件綁定
- 模板覆蓋的實際用例
讓我們開始吧!
什么是Vue render函數
Vue.js模板功能強大,幾乎可以滿足我們在應用程序中所需的一切。但是,有一些場景下,比如基於輸入或插槽值創建動態組件,render函數可以更好地滿足這些用例。
那些來自react世界的開發者可能對render函數非常熟悉。通常在jsX中使用它們來構建react組件。雖然Vue渲染函數也可以用JSX編寫,但我們將繼續使用原始JS,有助於我們可以更輕松地了解Vue組件系統的基礎。。
每個Vue組件都實現了一個render函數。大多數時候,該函數將由Vue編譯器創建。當我們在組件上指定模板時,該模板的內容將由Vue編譯器處理,編譯器最終將返回render函數。渲染函數本質上返回一個虛擬DOM節點,該節點將被Vue在瀏覽器DOM中渲染。
現在又引出了虛擬DOM的概念,"虛擬DOM到底是什么?"
虛擬文檔對象模型(或"DOM")允許Vue在更新瀏覽器之前在其內存中渲染組件。這使一切變得更快,同時也避免了DOM重新渲染的高昂成本。因為每個DOM節點對象包含很多屬性和方法,因此使用虛擬DOM預先在內存進行操作,可以省去很多瀏覽器直接創建DOM節點對象的開銷。
Vue更新瀏覽器DOM時,會將更新的虛擬DOM與上一個虛擬DOM進行比較,並僅使用已修改的部分更新實際DOM。這意味着更少的元素更改,從而提高了性能。Render函數返回虛擬DOM節點,在Vue生態系統中通常稱為VNode,該接口是允許Vue在瀏覽器DOM中寫入這些對象的接口。它們包含使用Vue所需的所有信息。 Vue的下一版本將包含一個全新的虛擬DOM實現,該實現將比目前更快。 React,Riot,Inferno等許多其他框架也使用虛擬DOM的概念。
我們可以在任何Vue組件中實現Vue render函數。同樣,由於Vue的數據響應性,每當組件的數據得到更新時,都會再次調用render函數。
這是一個簡單的示例,說明如何直接使用組件中的render函數去渲染h1標簽:
new Vue({ el: '#app', render(createElement) { return createElement('h1', 'Hello world'); } });
有一些內置組件可以利用渲染函數的功能,例如transition和keep-alive。這些組件直接在渲染函數中操縱VNode。如果Vue沒有提供這個函數特性,這些功能將無法實現。
Vue編譯器如何處理render函數?
大多數時候,Vue渲染函數將在項目構建期間由Vue編譯器進行編譯(例如,使用webpack)。因此,編譯器不會最終出現在您的生產代碼中,從而減小了包的體積。這就是為什么當您使用"單個文件組件"時,除非我們確實需要/想要,否則您實際上不需要使用render函數。
但是,如果我們想在代碼中使用編譯器,則可以使用帶有編譯器的Vue版本。簡而言之,我們正在使用Vue編譯器來編譯自定義模板。假設我們在做一個電商項目,那么可以將其注入購物車,從而可以擁有更多的控制。
我們編寫了一個實現自定義渲染功能的組件,該功能可獲取用戶創建的模板並替換我們的默認模板。
這是一個如何使用編譯器將模板字符串編譯為渲染函數的快速示例:
const template = `
<ul>
<li v-for="item in items"> {{ item }} </li> </ul>`; const compiledTemplate = Vue.compile(template); new Vue({ el: '#app', data() { return { items: ['Item1', 'Item2'] } }, render(createElement) { return compiledTemplate.render.call(this, createElement); } });
如您所見,編譯器將返回一個對象(compiledTemplate),其中包含准備使用的render函數。
創建一個組件
具有渲染功能的組件沒有模板標記或屬性。相反,他們定義了一個稱為render的函數,該函數接收一個createElement(renderElement:String | Component,define:Object,children:String | Array)參數(由於某種原因,通常別名為h,歸咎於JSX)並返回使用該函數創建的元素。其他一切保持不變。
export default { data() { return { isRed: true } }, /* * 和下邊使用template相同 * <template> * <div :is-red': isRed}"> * <p>Example Text</p> * </div> * </template> */ render(h) { return h('div', { 'class': { 'is-red': this.isRed } }, [ h('p', 'Example Text') ]) } }
在render函數中使用指令
Vue模板具有各種便捷功能,以便向模板添加基本邏輯和綁定功能。渲染函數無法訪問它們。取而代之的是,它們必須以純JavaScript實現,對於大多數指令而言,這是相當簡單的。
v-if
這個很簡單。除了使用v-if之外,還可以在createElement中調用普通的if(expr)語句。
<ul v-if="items.length"> <li v-for="item in items">{{ item.name }}</li> </ul> <p v-else>No items found.</p>
這些都可以在渲染函數中用JavaScript的if/else 和 map來重寫:
props: ['items'], render: function (h) { if (this.items.length) { return h('ul', this.items.map(function (item) { return h('li', item.name) })) } else { return h('p', 'No items found.') } }
v-for
v-for可以使用for-of,Array.map,Array.filter等多種迭代方法中的任何一種來實現。我們可以將它們以非常有趣的方式組合在一起,以實現過濾或分割數據。
例如,我們可以替換
<template> <ul> <li v-for="item of list"> </li> </ul> </template>
為
render(h) {
return h('ul', this.list.map(item => h('li', item.name))); }
v-model
要記住的一件事是,v-model就是綁定屬性為value(還可以是其他屬性),只要觸發input事件並設置data屬性的簡寫形式。不幸的是,渲染函數沒有這么簡寫。我們必須自己實現它,如下所示。
render(h) {
return h('input', { domProps: { value: this.myBoundProperty }, on: { input: e => { this.myBoundProperty = e.target.value } } }) }
等效於:
<template> <input :value="myBoundProperty" @input="myBoundProperty = $event.target.value"/> </template>
或
<template> <input v-model="myBoundProperty"/> </template>
v-bind
屬性和屬性綁定以attrs,props和domProps(類似於value和innerhtml之類)的形式放置在元素定義中。
render(h) {
return h('div', { attrs: { // <div :id="myCustomId"> id: this.myCustomId }, props: { // <div :someProp="someValue"> someProp: this.someValue }, domProps: { // <div :value="someValue"> value: this.someValue } }); }
附帶說明一下,類和樣式綁定直接在定義的根進行處理,而不是作為attrs,props或domProps處理。
render(h) {
return h('div', { // "class" 是JS中的保留字, 所以需要引號包起來. 'class': { myClass: true, theirClass: false }, style: { backgroundColor: 'green' } }); }
v-on
事件處理程序也可以直接在on(或nativeOn,與組件的v-on.native效果相同)中直接添加到元素定義中。
render(h) {
return h('div', { on: { click(e) { console.log('我點擊了!') } } }); }
- 修飾符可以在處理程序內部實現:
- .stop -> e.stopPropagation()
- .prevent -> e.preventDefault()
- .self -> if (e.target !== e.currentTarget) return
- 鍵盤修飾符
- .[TARGET_KEY_CODE] -> if (event.keyCode !== TARGET_KEY_CODE) return
- .[MODIFIER] -> if (!event.MODIFIERKey) return
插槽
可以通過this.$slots作為createElement()節點的數組來訪問插槽。
<div id="app"> <myslot> <div slot="slot1">this is slot1</div> <div slot="slot2">This is slot2 </div> </myslot> <script> var app = new Vue({ el: '#app' }) </script>
Vue.component('my-slot', { render: function(h) { const child = h('div', { domProps: {innerhtml: 'this is child'} }); return h( 'div', [ child, this.$slots.slot1, this.$slots.slot2 ] ) } })
作用域插槽存儲在this.$scopedSlots[scope](props:object)中,作為返回createElement()節點數組的函數。
<div id="app"> <myslot> <template scope="props"> <div>{{props.text}}</div> </template> </myslot> <script> var app = new Vue({ el: '#app' }) </script>
Vue.component('my-slot', { render: function(h) { const child = h('div', { domProps: {innerHTML: 'this is child'} }); return h( 'div', [ child, this.$scopedSlots.default({ text: 'hello scope' }) ] ) } })
Vue渲染函數中的事件綁定
createElement函數可以接收稱為數據對象的參數。該對象可以具有多個屬性,這些屬性與我們在標准模板中使用的v-bind:on等指令等效。這是帶有按鈕的簡單計數器組件的示例,該按鈕可以增加點擊次數。
new Vue({ el: '#app', data() { return { clickCount: 0, } }, methods: { onClick() { this.clickCount += 1; } }, render(h) { const button = h('button', { on: { click: this.onClick } }, 'Click me'); const counter = h('span', [ 'Number of clicks:', this.clickCount ]); return h('div', [ button, counter ]) } });
但是數據對象不限於事件綁定!我們也可以像使用v-bind:class指令一樣將css類應用於元素。
new Vue({ el: '#app', data() { return { clickCount: 0, } }, computed: { backgroundColor() { return { 'pink': this.clickCount%2 === 0, 'green': this.clickCount%2 !== 0, }; } }, methods: { onClick() { this.clickCount += 1; } }, render(h) { const button = h('button', { on: { click: this.onClick } }, 'Click me'); const counter = h('span', { 'class': this.backgroundColor, }, [ 'Number of clicks:', this.clickCount ]); return h('div', [ button, counter ]) } });
電腦刺綉綉花廠 http://www.szhdn.com 廣州品牌設計公司https://www.houdianzi.com
模板覆蓋的實際用例
我認為了解Vue的幕后工作非常有趣。要知道是否能夠最有效地使用工具,唯一的方法是確切地了解它的工作方式。
這並不是說我們應該開始將所有模板都轉換為render函數,但是有時它們可以派上用場,所以我們至少應該知道如何使用它們。
在上面的示例中,我展示了如何在組件中使用自定義render函數,該函數允許我們的某些組件可重寫。
首先,讓我們創建初始模板。
<div id="app"> <heading> <span>Heading title is: {{ title }}</span> </heading> </div>
在將要安裝Vue應用程序的div內,我們編寫一個自定義模板。接下來,我們希望模板覆蓋我們將要創建的標題組件的默認版本。
我們要做的第一件事是遍歷自定義模板,並使用Vue編譯器對其進行預編譯:
const templates = []; const templatesContainer = document.getElementById('app'); // 我們遍歷每個自定義模板並對其進行預編譯 for (var i = 0; i < templatesContainer.children.length; i++) { const template = templatesContainer.children.item(i); templates.push({ name: template.nodeName.toLowerCase(), renderFunction: Vue.compile(template.innerHTML), }); }
然后,讓我們創建標題組件:
Vue.component('heading', {
props: ['title'],
template: `
<overridable name="heading"> <h1> {{ title }} </h1> </overridable>` });
現在,它只是一個簡單的組件,具有一個名為title的屬性。默認模板將渲染帶有標題的h1。我們將用隨后創建的overridable組件包裝該組件。
這是我們將使用自定義渲染功能的地方。
Vue.component('overridable', { props: ['name'], render(createElement) { // 我們使用在初始化應用程序時創建的模板數組 const template = templates.find(x => x.name === this.name); // 當沒有自定義模板時,我們將使用默認插槽返回默認內容。 if (!template) { return this.$slots.default[0]; } // 使用預編譯的render函數 return template.renderFunction.render.call(this.$parent, createElement); } });
然后,讓我們掛載Vue應用程序:
new Vue({ el: '#app', template: `<heading title="Hello world"></heading>` });
在此示例中,我們可以看到使用了默認模板,因此它是一個標准的h1標簽。
如果將自定義模板添加到div#app內,則會看到標題組件會被渲染成我們指定的自定義模板。