Vue render深入窺探之謎

小小坤發表於2019-03-04

簡介

在使用Vue進行開發的時候,大多數情況下都是使用template進行開發,使用template簡單、方便、快捷,可是有時候需要特殊的場景使用template就不是很適合。因此為了很好使用render函式,我決定深入窺探一下。各位看官如果覺得下面寫的有不正確之處還望看官指出,你們與我的互動就是寫作的最大動力。
首發於:Vue render深入窺探之謎

場景

官網描述的場景當我們開始寫一個通過 level prop 動態生成 heading 標籤的元件,你可能很快想到這樣實現:

<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>
複製程式碼
Vue.component(`anchored-heading`, {
  template: `#anchored-heading-template`,
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
複製程式碼

在這種場景中使用 template 並不是最好的選擇:首先程式碼冗長,為了在不同級別的標題中插入錨點元素,我們需要重複地使用 。

雖然模板在大多陣列件中都非常好用,但是在這裡它就不是很簡潔的了。那麼,我們來嘗試使用 render 函式重寫上面的例子:

Vue.component(`anchored-heading`, {
  render: function (createElement) {
    return createElement(
      `h` + this.level,   // tag name 標籤名稱
      this.$slots.default // 子元件中的陣列
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
複製程式碼

簡單清晰很多!簡單來說,這樣程式碼精簡很多,但是需要非常熟悉 Vue 的例項屬性。在這個例子中,你需要知道當你不使用 slot 屬性向元件中傳遞內容時,比如 anchored-heading 中的 Hello world!,這些子元素被儲存在元件例項中的 $slots.default中。

createElement引數介紹

接下來你需要熟悉的是如何在 createElement 函式中生成模板。這裡是 createElement 接受的引數:

createElement(
  // {String | Object | Function}
  // 一個 HTML 標籤字串,元件選項物件,或者
  // 解析上述任何一種的一個 async 非同步函式,必要引數。
  `div`,

  // {Object}
  // 一個包含模板相關屬性的資料物件
  // 這樣,您可以在 template 中使用這些屬性。可選引數。
  {
    // (詳情見下一節)
  },

  // {String | Array}
  // 子節點 (VNodes),由 `createElement()` 構建而成,
  // 或使用字串來生成“文字節點”。可選引數。
  [
    `先寫一些文字`,
    createElement(`h1`, `一則頭條`),
    createElement(MyComponent, {
      props: {
        someProp: `foobar`
      }
    })
  ]
)
複製程式碼

深入 data 物件

有一件事要注意:正如在模板語法中,v-bind:class 和 v-bind:style ,會被特別對待一樣,在 VNode 資料物件中,下列屬性名是級別最高的欄位。該物件也允許你繫結普通的 HTML 特性,就像 DOM 屬性一樣,比如 innerHTML (這會取代 v-html 指令)。

{
  // 和`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
  },
  // 自定義指令。注意,你無法對 `binding` 中的 `oldValue`
  // 賦值,因為 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`
}
複製程式碼

條件渲染

既然熟讀以上api接下來我們們就來點實戰。
之前這樣寫

//HTML
<div id="app">
   <vv-isshow :show="isShow"></vv-isshow>
</div>

//js
//元件形式			
Vue.component(`vv-isshow`, {
	props:[`show`],
	template:`<div v-if="show">我被你發現啦2!!!</div>`,
});
var vm = new Vue({
	el: "#app",
	data: {
		isShow:true
	}
});
複製程式碼

render這樣寫

//HTML
<div id="app">
   <vv-isshow :show="isShow"><slot>我被你發現啦3!!!</slot></vv-isshow>
</div>
//js
//元件形式			
Vue.component(`vv-isshow`, {
	props:{
		show:{
			type: Boolean,
			default: true
		}
	},
	render:function(h){	
		if(this.show ) return h(`div`,this.$slots.default);
	},
});
var vm = new Vue({
	el: "#app",
	data: {
		isShow:true
	}
});
複製程式碼

列表渲染

之前是這樣寫的,而且v-for 時template內必須被一個標籤包裹

//HTML
<div id="app">
   <vv-aside v-bind:list="list"></vv-aside>
</div>
//js
//元件形式			
Vue.component(`vv-aside`, {
	props:[`list`],
	methods:{
		handelClick(item){
			console.log(item);
		}
	},
	template:`<div>
	              <div v-for="item in list" @click="handelClick(item)" :class="{odd:item.odd}">{{item.txt}}</div>
	          </div>`,
	//template:`<div v-for="item in list" @click="handelClick(item)" :class="{odd:item.odd}">{{item.txt}}</div>`,錯誤          
});
var vm = new Vue({
	el: "#app",
	data: {
		list: [{
			id: 1,
			txt: `javaScript`,
			odd: true
		}, {
			id: 2,
			txt: `Vue`,
			odd: false
		}, {
			id: 3,
			txt: `React`,
			odd: true
		}]
	}
});
複製程式碼

render這樣寫

//HTML
<div id="app">
   <vv-aside v-bind:list="list"></vv-aside>
</div>
//js
//側邊欄
Vue.component(`vv-aside`, {
	render: function(h) {
		var _this = this,
			ayy = this.list.map((v) => {
				return h(`div`, {
					`class`: {
						odd: v.odd
					},
					attrs: {
						title: v.txt
					},
					on: {
						click: function() {
							return _this.handelClick(v);
						}
					}
				}, v.txt);
			});
		return h(`div`, ayy);

	},
	props: {
		list: {
			type: Array,
			default: () => {
				return this.list || [];
			}
		}
	},
	methods: {
		handelClick: function(item) {
			console.log(item, "item");
		}
	}
});
var vm = new Vue({
	el: "#app",
	data: {
		list: [{
			id: 1,
			txt: `javaScript`,
			odd: true
		}, {
			id: 2,
			txt: `Vue`,
			odd: false
		}, {
			id: 3,
			txt: `React`,
			odd: true
		}]
	}
});
複製程式碼

v-model

之前的寫法

//HTML
<div id="app">
	<vv-models v-model="txt" :txt="txt"></vv-models>
</div>
//js
//input
Vue.component(`vv-models`, {
	props: [`txt`],
	template: `<div>
				  <p>看官你輸入的是:{{txtcout}}</p>
	              <input v-model="txtcout" type="text" />
	          </div>`,
	computed: {
		txtcout:{
			get(){
				return this.txt;
			},
			set(val){
				this.$emit(`input`, val);
			}
			
		}
	}
});
var vm = new Vue({
	el: "#app",
	data: {
	    txt: ``, 
	}
});
複製程式碼

render這樣寫

//HTML
<div id="app">
	<vv-models v-model="txt" :txt="txt"></vv-models>
</div>
//js
//input
Vue.component(`vv-models`, {
	props: {
		txt: {
			type: String,
			default: ``
		}
	},
	render: function(h) {
		var self=this;
		return h(`div`,[h(`p`,`你猜我輸入的是啥:`+this.txt),h(`input`,{
			on:{
				input(event){
                    self.$emit(`input`, event.target.value);
				}
			}
		})] );
	},
});
var vm = new Vue({
	el: "#app",
	data: {
	    txt: ``, 
	}
});
複製程式碼

總結

render函式使用的是JavaScript 的完全程式設計的能力,在效能上是佔用絕對的優勢,小編只是對它進行剖析。至於實際專案你選擇那種方式進行渲染依舊需要根據你的專案以及實際情況而定。

相關文章