一、Render函數
因此直接來到Render,原本也想跳過,發現後面的路由貌似跟它還有點關聯。先來看看Render
1.1 官網一開始就看的挺懵的,不知道講的是啥,動手試了一下,一開頭講的是Render的用法,官網的栗子永遠都是一個特色,tm的不貼完整,我這裏是個相對完整版的:(爲了看的清楚點,替換了下名字)javascript
<div id="div1"> <child :level="2">Hello world!</child> </div> <script type="text/x-template" id="template"> <div> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-if="level === 2"> <slot></slot> </h2> <h3 v-if="level === 3"> <slot></slot> </h3> <h4 v-if="level === 4"> <slot></slot> </h4> <h5 v-if="level === 5"> <slot></slot> </h5> <h6 v-if="level === 6"> <slot></slot> </h6> </div> </script> <script type="text/javascript"> Vue.component('child', { template: '#template', props: { level: { type: Number, required: true } } }) new Vue({ el:"#div1" }) </script>
回顧一下前面所學,這裏註冊了一個名叫child的全局組件,其模板是id=template的模板,往上一看發現,這個寫法跟之前不同啊,之前用的是<template>
標籤,小夥伴們還有印象不?爲此查了下api,也就是說這是新版寫法。模板裏有作了判斷,根據level的值來選擇head的尺寸h1-h6,同時使用slot分發內容(不記得的童鞋能夠看看我前面的文章)。level在哪裏?回頭看組件裏的props,這東東還有印象不,父組件傳遞參數給子組件能夠用它,同時還作了props驗證,level必須是Number類型,這個前面咱們也聊過的。html
最後實例化Vue,在id=div1的塊中使用Vue,這樣div1就可使用child模板:前端
<div id="div1"> <child :level="2">Hello world!</child> </div>
此時,父組件div1可使用子模板child,同時父模板可使用level屬性,採用bind的方式能夠傳遞數值2,不用:去bind的後果就是傳遞字符串」2」,這個也聊過了。hello world做爲slot分發的內容。因此最後整個內容會顯而易見的被渲染爲:。。。不寫了,本身研究。vue
忽然發現咱們的案例愈來愈複雜了,還好前面有作準備。可是這一切跟Render好像沒有半毛錢關係啊,確實沒有關係- -!官方舉了這個栗子就是說明這種寫法是繁雜浪費的,浪費的緣由是,雖然最後只剩下h2,可是其餘的h1,h3-h5其實都被渲染了,只不過沒有顯示而已。爲了優化,因此才引用了Render。java
1.2 將上面代碼改寫爲Render方式node
//html <div id="div1"> <child :level="3">Hello world!</child> </div> //script <script type="text/javascript"> Vue.component('child', { render: function (createElement) { return createElement( 'h' + this.level, // tag name 標籤名稱 this.$slots.default // 子組件中的陣列 ) }, props: { level: { type: Number, required: true } } }) new Vue({ el:"#div1" }) </script>
沒了?是的,沒了。不信你試一下,效果是同樣的,綁定1的話渲染出h1,綁定2渲染h2。我去,很6啊,模板都不要就搞定了。怎麼作到的?看createElement是個啥東東先,因此就開始createElement。因此,你們們發現了沒,這官網的邏輯就是非主流啊,無心中被我發現了要理解他的邏輯必須向我這樣邊試邊看才行,哇咔咔。不過咱們顧名思義一下,createElement看名字像動態建立dom節點(節點vue裏面也叫VNode)的過程,在看內容,’h’+this.level根據level建立標籤h1-h6,因此它只會渲染一個標籤,而不是全部都渲染,因此優化了,並且代碼也省了很多呢。git
1.3 createElement有點印象,js添加dom節點能夠用它,document.createElement(tag)。這裏的createElement(tag,{},[])或者createElement(tag,{},String)相似,不過接收的參數不同,後面兩個參數都是可選的github
// @returns {VNode} createElement( // {String | Object | Function} // 一個 HTML 標籤,組件設置,或一個函數 // 必須 Return 上述其中一個 'div', // {Object} // 一個對應屬性的數據對象 // 您能夠在 template 中使用.可選項. { // (下一章,將詳細說明相關細節) }, // {String | Array} // 子節點(VNodes). 可選項. [ createElement('h1', 'hello world'), createElement(MyComponent, { props: { someProp: 'foo' } }), 'bar' ] )
其中tag參數相似,第二個參數{}其實就一個數據對象,表明用在該節點的屬性,好比常見的class,style,props,on等,完整的數據對象以下:正則表達式
{ // 和`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 } } ], // 若是子組件有定義 slot 的名稱 slot: 'name-of-slot' // 其餘特殊頂層屬性 key: 'myKey', ref: 'myRef' }
第三個參數[]能夠看出來是表示該節點下面還有其餘的節點,就放在此處[createElement(tag1),createElement(tag2)]。ok,回頭看1.2中改寫的render方法,至關於用了createElement(tag,[])的形式,其中tag=’h’+this.level, []= this.$slots.default
。第一個參數好理解,第二個參數this.$slots.default
是什麼鬼,不知道的時候就去查api,slots很顯然就是用於分發的那些slot們,找到api中的slot。官方是這麼描述的:express
用來訪問被 slot 分發的內容。每一個具名 slot 有其相應的屬性(例如:slot="foo" 中的內容將會在 vm.$slots.foo 中被找到)。default 屬性包括了全部沒有被包含在一個具名 slot 中的節點。 在使用 render 函數書寫一個組件時,訪問 vm.$slots 最有幫助。
因此這貨其實表明的是不具名的slot內容,也就是[VNode1,VNode2…]數組,這裏的只有一個VNode就是那句被child包裹的Hello world!因此1.2中的render最後渲染的結果其實就是一個<h1>Hello world!</h1>
這樣的節點。
1.4 原文後面給了個完整例子不描述了,不同的地方在於建立a標籤的時候使用了(tag,{},[])結構
createElement('a', { attrs: { name: headingId, href: '#' + headingId } }, this.$slots.default)
var getChildrenTextContent = function (children) { return children.map(function (node) { return node.children ? getChildrenTextContent(node.children) : node.text }).join('') } var headingId = getChildrenTextContent(this.$slots.default) .toLowerCase() .replace(/\W+/g, '-') .replace(/(^\-|\-$)/g, '')
getChildrenTextContent 這個函數,由於this.$slots.default
是個數組[VNode1,VNode2…],因此能夠作map處理(印象中是SE6方法),對數組中的每一個元素作統一處理:遞歸,一層層去查看VNode是否有子節點,有子節點就調用自身,直到無子節點後取出他的文本內容。最後用數組的join方法把每一層的文本用空格符鏈接
好比
<div id="div1"> <child :level="1"> Hello world! <h2> woqu <h3>what</h3> </h2> </child> </div>
this.$slots.default
的值是[VNode1,VNode2,VNode3],其中
VNode1 = Hello world!
VNode2 = <h2>woqu</h2>
VNode3 = <h3>what</h3>
VNode1沒child,直接返回了Hello world!,VNode2有child是h2,因此遞歸了一次h2裏面沒child,返回了woqu,VNode3狀況相似,最終返回了what。因此map的結果就是獲得了一個數組[‘Hello world!’,’woqu’,’what’],而後調用join方法串起來,獲得’Hello world! woqu what’;
後面再進行.toLowerCase()轉小寫,變爲’hello world! woqu what’;
再replace(/\W+/g, '-')
進行正則替換,正則對於搞it的來講應該不陌生,js中的正則格式是這樣的,/正則表達式/匹配模式,匹配模式固然是可選的,\W表示非單詞字符(0-9,a-z,A-Z,_),+表示一個或多個,/g表示使用全局匹配模式,全局的特色是每次匹配完,下次匹配的下標就是下一位,因此此次替換會把連續的非單詞字符替換爲-,變爲’hello-world-woqu-what’;
再使用replace(/(^\-|\-$)/g, '')
作一次正則替換,^\-
表示匹配開頭的-字符,\-$
表示匹配結尾的-字符,|表示或者,這句的意思是若是字符串開頭或結尾有-,就把他們替換成」,也就是直接刪除,因而這裏沒有變化’hello-world-woqu-what’。
綜上所述,var headingId = ‘hello-world-woqu-what’。
1.5 VNodes 必須惟一。這句話說的不是很清楚,其實就是同一個VNode只能用在一個地方。
好比
render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // Yikes - duplicate VNodes! myParagraphVNode, myParagraphVNode ]) }
這裏的myParagraphVNode,被使用於’div’中的兩個VNode,這種用法是不行,要想用只能建立兩個相同的VNode對象,而不是這樣指向同一個VNode對象。
1.6 Render之函數化組件
大概是這個意思,看看1.5的render的結構,
render:function(createElement){} 這個結構能夠建立VNode對吧,可是沒法訪問外部數據,若是但願建立的VNode須要依賴外部數據怎麼辦?這就是這一節的內容。
將其改寫爲如下方式,就能夠訪問外部數據了:
Vue.component('my-component', { functional: true, //1 // 爲了彌補缺乏的實例 // 提供第二個參數做爲上下文 render: function (createElement, context) { //2 // ... }, })
經過1和2兩個改寫,就能夠利用context去訪問外部數據了,context至關於一個組件的上下文,能夠訪問該組件的一些數據:
props: 提供props 的對象
children: VNode 子節點的數組
slots: slots 對象
data: 傳遞給組件的 data 對象
parent: 對父組件的引用
好比:this.$slots.default 更新爲 context.children,以後this.level 更新爲 context.props.level。
差很少就是這個意思
1.7 模板編譯過程
這裏講到一些vue模板底層在生命週期的編譯階段Vue.compile的處理方式。
好比模板:
<div>
<h1>I'm a template!</h1>
<p v-if="message">
{{ message }}
</p>
<p v-else>
No message.
</p>
</div>
在編譯的時候會相似如下的處理
能夠看出div被建立的時候,相似於createElement,傳了VNodes數組給他,_m(0)就是第一個節點VNode<h1>I'm a template!</h1>
後面的參數是個選擇運算符a?b:c,若是message爲true,則建立一個p節點,若是爲false,也建立一個p節點,只不錯兩個p節點內容不同
另外能夠爲createElement取別名,通常用h表示
1.7 JSX
這個東東做爲我這樣的前端小白,之前是沒有聽過的。查了一下,JSX語法,像是在Javascript代碼裏直接寫XML的語法,每個XML標籤都會被JSX轉換工具轉換成純Javascript代碼。看下面的例子:
//不使用JSX的狀況下可能要這麼寫 render: function (h) { h( 'div', [ h('span', 'Hello'), ' world!' ] ) } //使用JSX能夠像寫xml或html這類標籤語言同樣直接寫,是否是直觀不少 render (h) { return ( <div> <span>Hello</span> world! </div> ) };
Vue中使用JSX須要這個插件 :Babel plugin 。https://github.com/vuejs/babel-plugin-transform-vue-jsx
原文地址:https://blog.csdn.net/kkae8643150/article/details/52910389