理解Vue中的Render渲染函數javascript
VUE通常使用template來建立HTML,而後在有的時候,咱們須要使用javascript來建立html,這時候咱們須要使用render函數。
好比以下我想要實現以下html:html
<div id="container"> <h1> <a href="#"> Hello world! </a> </h1> </div>
咱們會以下使用:vue
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading :level="1"> <a href="#">Hello world!</a> </tb-heading> </div> </body> <script src="./vue.js"></script> <script type="text/x-template" id="templateId"> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> </script> <script> Vue.component('tb-heading', { template: '#templateId', props: { level: { type: Number, required: true } } }); new Vue({ el: '#container' }); </script> </html>
查看效果java
如上代碼是根據參數 :level來顯示不一樣級別的標題中插入錨點元素,咱們須要重複的使用 <slot></slot>.node
下面咱們來嘗試使用 render函數重寫上面的demo;以下代碼:git
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading :level="2"> <a href="#">Hello world!</a> </tb-heading> </div> </body> <script src="./vue.js"></script> <script> Vue.component('tb-heading', { render: function(createElement) { return createElement( 'h' + this.level, // tag name 標籤名稱 this.$slots.default // 組件的子元素 ) }, props: { level: { type: Number, required: true } } }); new Vue({ el: '#container' }); </script> </html>
查看效果github
如上 render函數代碼看起來很是簡單就實現了,組件中的子元素存儲在組件實列中 $slots.default 中。express
理解createElement
Vue經過創建一個虛擬DOM對真實的DOM發生變化保存追蹤,以下代碼:
return createElement('h1', this.title);
createElement返回的是包含的信息會告訴VUE頁面上須要渲染什麼樣的節點及其子節點。咱們稱這樣的節點爲虛擬DOM,能夠簡寫爲VNode,api
createElement 參數 // @return {VNode} createElement( // {String | Object | Function} // 一個HTML標籤字符串,組件選項對象,或者一個返回值類型爲String/Object的函數。該參數是必須的 'div', // {Object} // 一個包含模板相關屬性的數據對象,這樣咱們能夠在template中使用這些屬性,該參數是可選的。 { }, // {String | Array} // 子節點(VNodes)由 createElement() 構建而成。可選參數 // 或簡單的使用字符串來生成的 "文本節點"。 [ 'xxxx', createElement('h1', '一則頭條'), createElement(MyComponent, { props: { someProp: 'xxx' } }) ] )
理解深刻data對象。
在模板語法中,咱們可使用 v-bind:class 和 v-bind:style 來綁定屬性,在VNode數據對象中,下面的屬性名的字段級別是最高的。
該對象容許咱們綁定普通的html特性,就像DOM屬性同樣。以下:數組
{ // 和`v-bind:class`同樣的 API 'class': { foo: true, bar: false }, // 和`v-bind:style`同樣的 API style: { color: 'red', fontSize: '14px' }, // 正常的 HTML 特性 attrs: { id: 'foo' }, // 組件 props props: { myProp: 'bar' }, // DOM 屬性 domProps: { innerHTML: 'baz' }, // 事件監聽器基於 `on` // 因此再也不支持如 `v-on:keyup.enter` 修飾器 // 須要手動匹配 keyCode。 on: { click: this.clickHandler }, // 僅對於組件,用於監聽原生事件,而不是組件內部使用 `vm.$emit` 觸發的事件。 nativeOn: { click: this.nativeClickHandler }, // 自定義指令。注意事項:不能對綁定的舊值設值 // Vue 會爲您持續追蹤 directives: [ { name: 'my-custom-directive', value: '2', expression: '1 + 1', arg: 'foo', modifiers: { bar: true } } ], // Scoped slots in the form of // { name: props => VNode | Array<VNode> } scopedSlots: { default: props => createElement('span', props.text) }, // 若是組件是其餘組件的子組件,需爲插槽指定名稱 slot: 'name-of-slot', // 其餘特殊頂層屬性 key: 'myKey', ref: 'myRef' }
上面的data數據可能不太好理解,咱們來看一個demo,就知道它是如何使用的了,以下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading :level="2"> Hello world! </tb-heading> </div> </body> <script src="./vue.js"></script> <script> var getChildrenTextContent = function(children) { return children.map(function(node) { return node.children ? getChildrenTextContent(node.children) : node.text }).join('') }; Vue.component('tb-heading', { render: function(createElement) { var headingId = getChildrenTextContent(this.$slots.default) .toLowerCase() .replace(/\W+/g, '-') .replace(/(^\-|\-$)/g, '') return createElement( 'h' + this.level, [ createElement('a', { attrs: { name: headingId, href: '#' + headingId }, style: { color: 'red', fontSize: '20px' }, 'class': { foo: true, bar: false }, // DOM屬性 domProps: { innerHTML: 'baz' }, // 組件props props: { myProp: 'bar' }, // 事件監聽基於 'on' // 因此再也不支持如 'v-on:keyup.enter' 修飾語 // 須要手動匹配 KeyCode on: { click: function(event) { event.preventDefault(); console.log(111); } } }, this.$slots.default) ] ) }, props: { level: { type: Number, required: true } } }); new Vue({ el: '#container' }); </script> </html>
對應的屬性使用方法和上面同樣既能夠了,咱們能夠打開頁面查看下效果也是能夠的。以下
VNodes 不必定必須惟一 (文檔中說要惟一)
文檔中說 VNode必須惟一;說 下面的 render function 是無效的:
可是我經過測試時能夠的,以下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading :level="2"> Hello world! </tb-heading> </div> </body> <script src="./vue.js"></script> <script> Vue.component('tb-heading', { render: function(createElement) { var pElem = createElement('p', 'hello world'); return createElement('div', [ pElem, pElem ]) }, props: { level: { type: Number, required: true } } }); new Vue({ el: '#container' }); </script> </html>
使用Javascript代替模板功能
v-if 和 v-for
template 中有 v-if 和 v-for, 可是vue中的render函數沒有提供專用的API。
好比以下:
<ul v-if="items.length"> <li v-for="item in items">{{ item.name }}</li> </ul> <p v-else>No item found.</p>
在render函數中會被javascript的 if/else 和map從新實現。以下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading> Hello world! </tb-heading> </div> </body> <script src="./vue.js"></script> <script> Vue.component('tb-heading', { render: function(createElement) { console.log(this) if (this.items.length) { return createElement('ul', this.items.map(function(item){ return createElement('li', item.name); })) } else { return createElement('p', 'No items found.'); } }, props: { items: { type: Array, default: function() { return [ { name: 'kongzhi1' }, { name: 'kongzhi2' } ] } } } }); new Vue({ el: '#container' }); </script> </html>
v-model
render函數中沒有 與 v-model相應的api,咱們必須本身來實現相應的邏輯。以下代碼能夠實現:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading @input="inputFunc"> Hello world! </tb-heading> </div> </body> <script src="./vue.js"></script> <script> Vue.component('tb-heading', { render: function(createElement) { var self = this; return createElement('input', { domProps: { value: '11' }, on: { input: function(event) { self.value = event.target.value; self.$emit('input', self.value); } } }) }, props: { } }); new Vue({ el: '#container', methods: { inputFunc: function(value) { console.log(value) } } }); </script> </html>
理解插槽
能夠從 this.$slots 獲取VNodes列表中的靜態內容:以下代碼:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> <tb-heading :level="2"> <a href="#">Hello world!</a> </tb-heading> </div> </body> <script src="./vue.js"></script> <script> Vue.component('tb-heading', { render: function(createElement) { return createElement( 'h' + this.level, // tag name 標籤名稱 this.$slots.default // 子組件 ) }, props: { level: { type: Number, required: true } } }); new Vue({ el: '#container' }); </script> </html>
理解函數式組件
函數式組件咱們標記組件爲 functional, 意味着它無狀態(沒有data), 無實列(沒有this上下文)。
一個函數式組件像下面這樣的:
Vue.component('my-component', { functional: true, // 爲了彌補缺乏的實列 // 提供第二個參數做爲上下文 render: function(createElement, context) { }, // Props 可選 props: { } })
組件須要的一切經過上下文傳遞,包括以下:
props: 提供props對象
children: VNode子節點的數組
slots: slots對象
data: 傳遞給組件的data對象
parent: 對父組件的引用
listeners: (2.3.0+) 一個包含了組件上所註冊的 v-on 偵聽器的對象。這只是一個指向 data.on 的別名。
injections: (2.3.0+) 若是使用了 inject 選項,則該對象包含了應當被注入的屬性。
在添加 functional: true 以後,組件的 render 函數之間簡單更新增長 context 參數,this.$slots.default 更新爲 context.children,以後this.level 更新爲 context.props.level。
以下代碼演示:
<!DOCTYPE html> <html> <head> <title>演示Vue</title> <style> </style> </head> <body> <div id="container"> {{msg}} <choice> <item value="1">test</item> </choice> </div> </body> <script src="./vue.js"></script> <script> Vue.component('choice', { template: '<div><ul><slot></slot></ul></div>' }); Vue.component('item', { functional: true, render: function(h, context) { return h('li', { on: { click: function() { console.log(context); console.log(context.parent); console.log(context.props) } } }, context.children) }, props: ['value'] }) new Vue({ el: '#container', data: { msg: 'hello' } }); </script> </html>