理解Vue中的Render渲染函數:https://www.cnblogs.com/tugenhua0707/p/7528621.html
理解Vue中的Render渲染函數
VUE一般使用template來創建HTML,然后在有的時候,我們需要使用javascript來創建html,這時候我們需要使用render函數。
比如如下我想要實現如下html:
<div id="container"> <h1> <a href="#"> Hello world! </a> </h1> </div>
我們會如下使用:
<!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>
如上代碼是根據參數 :level來顯示不同級別的標題中插入錨點元素,我們需要重復的使用 <slot></slot>.
下面我們來嘗試使用 render函數重寫上面的demo;如下代碼:
<!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>
如上 render函數代碼看起來非常簡單就實現了,組件中的子元素存儲在組件實列中 $slots.default 中。
理解createElement
Vue通過建立一個虛擬DOM對真實的DOM發生變化保存追蹤,如下代碼:
return createElement('h1', this.title);
createElement返回的是包含的信息會告訴VUE頁面上需要渲染什么樣的節點及其子節點。我們稱這樣的節點為虛擬DOM,可以簡寫為VNode,
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>