相信你們都或多或少的在 code 中見過 或使用過 Render,若是你對它仍是一臉懵逼,那就快上車!今天就帶你來盤它。vue
@[toc]node
Render 函數是 Vue2.x 新增的一個函數、主要用來提高節點的性能,它是基於 JavaScript 計算。使用 Render 函數將 Template 裏面的節點解析成虛擬的 Dom 。express
Vue 推薦在絕大多數狀況下使用模板來建立你的 HTML。然而在一些場景中,你真的須要 JavaScript 的徹底編程的能力。這時你能夠用渲染函數,它比模板更接近編譯器。編程
簡單的說,在 Vue 中咱們使用模板 HTML 語法組建頁面的,使用 Render 函數咱們能夠用 Js 語言來構建 DOM。數組
由於 Vue 是虛擬 DOM,因此在拿到 Template 模板時也要轉譯成 VNode 的函數,而用 Render 函數構建 DOM,Vue 就免去了轉譯的過程。瀏覽器
你第一次邂逅它的時候,它多是這樣的:bash
render:(h, params)=>{ return h('div', {style:{width:'100px',height:'100px',background:'#ccc'}}, '地方') } 複製代碼
<el-table-column :render-header="setHeader"> </el-table-column> setHeader (h) { return h('span', [ h('span', { style: 'line-height: 40px;' }, '備註'), h('el-button', { props: { type: 'primary', size: 'medium', disabled: this.isDisable || !this.tableData.length }, on: { click: this.save } }, '保存當前頁') ]) ]) }, 複製代碼
或者這樣的:markdown
renderContent (createElement, { node, data, store }) { return createElement('span', [ // 顯示樹的節點信息 createElement('span', node.label) // ...... ]) } 複製代碼
那它的真身究竟是什麼樣的呢?這還要從它的身世提及。app
在深刻渲染函數以前,瞭解一些瀏覽器的工做原理是很重要的。如下面這段 HTML 爲例:dom
<div>
<h1>My title</h1>
Some text content
<!-- TODO: Add tagline -->
</div>
複製代碼
當瀏覽器讀到這些代碼時,它會創建一個DOM 節點樹來保持追蹤全部內容,如同你會畫一張家譜樹來追蹤家庭成員的發展同樣。
上述 HTML 對應的 DOM 節點樹以下圖所示:
每一個元素都是一個節點。每段文字也是一個節點。甚至註釋也都是節點。一個節點就是頁面的一個部分。就像家譜樹同樣,每一個節點均可以有孩子節點 (也就是說每一個部分能夠包含其它的一些部分)。高效地更新全部這些節點會是比較困難的,不過所幸你沒必要手動完成這個工做。你只須要告訴 Vue 你但願頁面上的 HTML 是什麼,這能夠是在一個模板裏:
<h1>{{ blogTitle }}</h1>
複製代碼
或者一個渲染函數裏:
render: function (createElement) { return createElement('h1', this.blogTitle) } 複製代碼
在這兩種狀況下,Vue 都會自動保持頁面的更新,即使 blogTitle 發生了改變。
Vue 經過創建一個虛擬 DOM 來追蹤本身要如何改變真實 DOM。請仔細看這行代碼:
return createElement('h1', this.blogTitle) 複製代碼
createElement
到底會返回什麼呢?其實不是一個_實際的_ DOM 元素。它更準確的名字多是 createNodeDescription
,由於它所包含的信息會告訴 Vue 頁面上須要渲染什麼樣的節點,包括及其子節點的描述信息。咱們把這樣的節點描述爲「虛擬節點 (virtual node)」,也常簡寫它爲「VNode」。「虛擬 DOM」是咱們對由 Vue 組件樹創建起來的整個 VNode 樹的稱呼。
注:==當使用render函數描述虛擬 DOM 時,vue 提供一個函數,這個函數是就構建虛擬 DOM 所須要的工具。官網上給他起了個名字叫 createElement。還有約定的簡寫叫 h,將 h 做爲 createElement 的別名是 Vue 生態系統中的一個通用慣例,實際上也是 JSX 所要求的。==
有點意思~ 其實它就是
createElement
,接下來讓咱們來走近一點點,來深刻的瞭解它吧~
createElement(TagName,Option,Content)接受三個參數
createElement(" 定義的元素 ",{ 元素的性質 }," 元素的內容"/[元素的內容])
// @returns {VNode} createElement( // {String | Object | Function} // 一個 HTML 標籤名、組件選項對象,或者 // resolve 了上述任何一種的一個 async 函數。必填項。 'div', // {Object} // 一個與模板中屬性對應的數據對象。可選。 { // (詳情見下一節-3.2 深刻數據對象) }, // {String | Array} // 子級虛擬節點 (VNodes),由 `createElement()` 構建而成, // 也可使用字符串來生成「文本虛擬節點」。可選。 [ '先寫一些文字', createElement('h1', '一則頭條'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] ) 複製代碼
{ // 與 `v-bind:class` 的 API 相同, // 接受一個字符串、對象或字符串和對象組成的數組 'class': { foo: true, bar: false }, // 與 `v-bind:style` 的 API 相同, // 接受一個字符串、對象,或對象組成的數組 style: { color: 'red', fontSize: '14px' }, // 普通的 HTML 特性 attrs: { id: 'foo' }, // 組件 prop props: { myProp: 'bar' }, // DOM 屬性 domProps: { innerHTML: 'baz' }, // 事件監聽器在 `on` 屬性內, // 但再也不支持如 `v-on:keyup.enter` 這樣的修飾器。 // 須要在處理函數中手動檢查 keyCode。 on: { click: this.clickHandler }, // 僅用於組件,用於監聽原生事件,而不是組件內部使用 // `vm.$emit` 觸發的事件。 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', // 其它特殊頂層屬性 key: 'myKey', ref: 'myRef', // 若是你在渲染函數中給多個元素都應用了相同的 ref 名, // 那麼 `$refs.myRef` 會變成一個數組。 refInFor: true } 複製代碼
render:(h) => { return h('div',{    //給div綁定value屬性 props: { value:'' },    //給div綁定樣式    style:{      width:'30px'    },     //給div綁定點擊事件   on: { click: () => { console.log('點擊事件') } }, }) } 複製代碼
它也是有小脾氣的~ 要記得這個約束喲~
組件樹中的全部 VNode 必須是惟一的。這意味着,下面的渲染函數是不合法的:
render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 錯誤 - 重複的 VNode myParagraphVNode, myParagraphVNode ]) } 複製代碼
若是你真的須要重複不少次的元素/組件,你可使用工廠函數來實現。例如,下面這渲染函數用徹底合法的方式渲染了 20 個相同的段落:
render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) } 複製代碼
上面的都是基礎,你們要記牢喲,下面介紹一些它的特性
只要在原生的 JavaScript 中能夠輕鬆完成的操做,Vue 的渲染函數就不會提供專有的替代方法。好比,在模板中使用的 v-if
和 v-for
:
<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 (createElement) { if (this.items.length) { return createElement('ul', this.items.map(function (item) { return createElement('li', item.name) })) } else { return createElement('p', 'No items found.') } } 複製代碼
渲染函數中沒有與 v-model 的直接對應——你必須本身實現相應的邏輯:
props: ['value'], render: function (createElement) { var self = this return createElement('input', { domProps: { value: self.value }, on: { input: function (event) { self.$emit('input', event.target.value) } } }) } 複製代碼
這就是深刻底層的代價,但與 v-model 相比,這可讓你更好地控制交互細節。
對於 .passive
、.capture
和 .once
這些事件修飾符, Vue 提供了相應的前綴能夠用於 on
:
修飾符 | 前綴 |
---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once 或.once.capture |
~! |
例如:
on: { '!click': this.doThisInCapturingMode, '~keyup': this.doThisOnce, '~!mouseover': this.doThisOnceInCapturingMode } 複製代碼
對於全部其它的修飾符,私有前綴都不是必須的,由於你能夠在事件處理函數中使用事件方法:
修飾符 | 處理函數中的等價操做 |
---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if (event.target !== event.currentTarget) return |
按鍵:.enter , .13 |
if (event.keyCode !== 13) return (對於別的按鍵修飾符來講,可將 13 改成另外一個按鍵碼) |
修飾鍵:.ctrl , .alt , .shift , .meta |
if (!event.ctrlKey) return (將 ctrlKey 分別修改成 altKey 、shiftKey 或者 metaKey ) |
這裏是一個使用全部修飾符的例子:
on: { keyup: function (event) { // 若是觸發事件的元素不是事件綁定的元素 // 則返回 if (event.target !== event.currentTarget) return // 若是按下去的不是 enter 鍵或者 // 沒有同時按下 shift 鍵 // 則返回 if (!event.shiftKey || event.keyCode !== 13) return // 阻止 事件冒泡 event.stopPropagation() // 阻止該元素默認的 keyup 事件 event.preventDefault() // ... } } 複製代碼
你能夠經過 this.$slots
訪問靜態插槽的內容,每一個插槽都是一個 VNode 數組:
render: function (createElement) { // `<div><slot></slot></div>` return createElement('div', this.$slots.default) } 複製代碼
也能夠經過 this.$scopedSlots
訪問做用域插槽,每一個做用域插槽都是一個返回若干 VNode 的函數:
props: ['message'], render: function (createElement) { // `<div><slot :text="message"></slot></div>` return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ]) } 複製代碼
若是要用渲染函數向子組件中傳遞做用域插槽,能夠利用 VNode 數據對象中的 scopedSlots
字段:
render: function (createElement) { return createElement('div', [ createElement('child', { // 在數據對象中傳遞 `scopedSlots` // 格式爲 { name: props => VNode | Array<VNode> } scopedSlots: { default: function (props) { return createElement('span', props.text) } } }) ]) } 複製代碼
Element 中的 Tree
// 樹節點的內容區的渲染回調 renderContent(h, { node, data, store }) { let aa = () => { console.log(data) } return h('span', [ h('span', { class: "custom-tree-node" }, [ h('i', { class: "icon-folder" }), h('span', { props: { title: node.label }, class: "text ellipsis" }, node.label), h('el-popover', { props: { placement: "bottom", title: "", width: "61", popperClass: "option-group-popover", trigger: "hover" } }, [ h('ul', { class: "option-group" }, [ h('li', { class: "pointer-text", on: { click: aa } }, '編輯'), h('li', { class: "pointer-text" }, '刪除'), h('li', { class: "pointer-text" }, '添加') ]), h('i', { slot: "reference", class: "el-icon-more fr more-icon", on: { click: (e) => { e.stopPropagation(); } } }) ]) ]) ]) }, 複製代碼
若是你寫了不少 render 函數,可能會以爲下面這樣的代碼寫起來很痛苦:
createElement( 'anchored-heading', { props: { level: 1 } }, [ createElement('span', 'Hello'), ' world!' ] ) 複製代碼
特別是對應的模板如此簡單的狀況下:
<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading> 複製代碼
這就是爲何會有一個 Babel 插件,用於在 Vue 中使用 JSX 語法,它可讓咱們回到更接近於模板的語法上。
import AnchoredHeading from './AnchoredHeading.vue' new Vue({ el: '#demo', render: function (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } }) 複製代碼