前言
在學習了解元件複用的時候檢視資料,看到了
mixins,extend
方法同時也查到了
進階函數(Higher-Order Components)和jsx
等字眼。聽上去hoc和jsx有種進階的感覺,那得好好研究下。
概念
高階元件定義 : 高階元件是一個方法,這個方法接收一個原始元件作為參數,并傳回新的元件。
jsx定義 : JSX 是一種類似于 XML 的 JavaScript 文法擴充 JSX 不是由引擎或浏覽器實作的。相反,我們将使用像 Babel 這樣的轉換器将 JSX 轉換為正常 JavaScript。基本上,JSX 允許我們在 JavaScript 中使用類似 HTML 的文法。
template模闆生成方式
Vue 推薦使用在絕大多數情況下使用 template 來建立你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全程式設計的能力,這就是 render 函數,它比 template 更接近編譯器。
其實我們傳入template模闆之後,在
vue底層也會将其轉換成render函數
vue源碼 src/platform/web/entry-runtime-with-compiler.js檔案中代碼如下,我們隻需要
關注tempalte變量
,首先判斷配置項中有無傳入render,如果沒有也會有很多種情況,無論哪種情況都會給template變量
指派一個DOM字元串
,最終通過
compileToFunctions函數将其轉換成render函數
,通過
結構指派的方式指派給render變量
,然後添加到執行個體上 具體可以看這篇文章
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
} else if (template.nodeType) {
template = template.innerHTML
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
}
}
複制代碼
render文法
使用之前需要的配置和使用文法這裡我們主要來看
props 、class、on(事件)、slots
這四個屬性,首先來看代碼
// 子元件
<script>
export default {
name: 'judge',
props: {
type: {
type: Number | String,
default: 1
}
},
render(h) {
return h(
'div',
{
class: {
default: true,
first: this.type == 1,
second: this.type == 2,
third: this.type == 3
},
on: {
click: this.onShowTypeClick
}
},
[this.$slots.default, this.$slots.tag]
)
},
methods: {
onShowTypeClick() {
console.log(this.type)
}
}
}
</script>
<style hljs-string">'scss' scoped>
.default {
text-align: center;
width: 200px;
line-height: 50px;
background-color: #eee;
}
.first {
background-color: #f66;
}
.second {
background-color: #c66;
}
.third {
background-color: #804;
}
</style>
複制代碼
// 父元件
<input type="text"
v-model="number">
<judge :type="number">
<span class="render__incoming"> 外來者</span>
<p class="render__incoming-p"
slot="tag">我是p标簽</p>
</judge>
複制代碼
render函數的
功能建立虛拟節點
就是代替template是以 在.vue 檔案中
必須省略template标簽
。
createElement
render函數預設接受
createElement 函數
createElement預設接受三個參數
createElement(
// {String | Object | Function}
// 一個 HTML 标簽字元串,元件選項對象,或者
// 解析上述任何一種的一個 async 異步函數。必需參數。
'div',
// {Object}
// 一個包含模闆相關屬性的資料對象
// {String | Array}
// 子虛拟節點 (VNodes),由 `createElement()` 建構而成,
[
'先寫一些文字',
createElement('h1', '一則頭條'),
this.$slots.default
]
)
複制代碼
除了第一個參數,
後面兩個都為可選參數
,第二個參數是
配置選項
如:style、attrs、props等,
注意的是使用了domProps屬性下的innerHTML後,會覆寫子節點和slot插入的元素
,
第三個參數是關于子節點
如:再用createElement函數建立一個虛拟子節點,或者是接受slot傳遞過來的DOM節點
hoc高階函數
同樣來看
props 、class、on(事件)、slots
這四個屬性的實作。
// hoc 元件
export default componentA => {
return {
// hoc 元件本身沒有設定props 需要設定傳入的元件相同的props
props: componentA.props,
render(h) {
let slots = {}
Object.keys(this.$slots).map(item => {
slots[item] = () => this.$slots[item]
return slots
})
return h(componentA, {
attrs: this.$attrs,
props: this.$props,
scopedSlots: slots,
on:this.$listeners
})
}
}
}
複制代碼
// 父元件
<packer :test="15"
v-on:customize-click="onCuClick">
插入資訊
<p slot="test">1516545</p>
</packer>
複制代碼
// componentA 元件
<template>
<div>
<span @click="handleClick">props: {{prop}}</span>
<slot name="test"></slot>
============
<slot></slot>
</div>
</template>
複制代碼
實作過程
在vue中
元件可以看作是一個對象
,裡面包括一些方法屬性如:
data、props、methods、鈎子函數等
,那hoc函數最終也是要傳回這樣一個對象。我們重新來溫習下hoc高階元件的定義,
一個函數接收一個元件,并且傳回一個新的元件
。可以這樣了解,
接受一個元件,并且對整個元件進行包裝然後傳回出去
。當然hoc函數傳回出去的對象應該也具有元件應該有的options如:
data、props、methods、鈎子函數等
。
來解釋下本例中的實作過程,給hoc函數設定props和
componentA
相同的props屬性,這樣我們也可以通過packer向hoc元件傳入props。在hoc接受到外部傳入的值後通過this.$props将hoc執行個體中的props值都傳遞給
componentA
,當然我們也可以給hoc設定多于
componentA
的props值。
attrs 指的是那些沒有被聲明為 props 的屬性
通過scopedSlots實作hoc元件傳入slot插槽
scopedSlots: {
// 實作預設插槽和具名插槽
default: ()=>this.$slots.default,
test: ()=>this.$slots.test
}
複制代碼
listeners: (2.3.0+) 一個包含了所有在父元件上注冊的事件偵聽器的對象。這隻是一個指向 data.on 的别名。
listeners指的就是在父元件上綁定的所有事件(是一個對象)格式為 { [key: string]: Function | Array } 鍵名為字元串,值為函數多個用數組
<packer v-on:customize-click="onCuClick"></packer>
複制代碼
是以我們需要在hoc元件中設定
componentA
的父元件事件
jsx
首先我們來看下用render函數實作使用element-ui table元件
export default {
render(h) {
return h(
'el-table',
{
props: {
data: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區金沙江路 1518 弄'
}
]
}
},
[
h('el-table-column', {
attrs: {
prop: 'date',
label: '日期',
width: '180'
}
})
]
)
}
複制代碼
可以看到隻是兩次嵌套關系的render函數寫起來的代碼很難維護,這裡就要用到jsx文法。它可以讓我們回到更接近于模闆的文法上。
export default {
data() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀區金沙江路 1518 弄'
}
]
}
},
render(h) {
return (
<el-table data={this.tableData}>
<el-table-column prop="date" label="日期" width="180" />
</el-table>
)
}
}
複制代碼
實作jsx中v-model
<script>
export default {
data() {
return {
initValue: ''
}
},
render() {
return (
<div class="input__wrap">
<div>initValue的值:{this.initValue}</div>
<el-input
value={this.initValue}
on-input={val => {
this.initValue = val
}}
/>
</div>
)
}
}
</script>
<style hljs-string">'scss' scoped>
.input__wrap {
width: 200px;
}
</style>
複制代碼
實作jsx中 v-for
<script>
export default {
data() {
return {
tagList: ['标簽1', '标簽2', '标簽3']
}
},
render() {
return (
<div class="tag__wrap">
{this.tagList.map((item, index) => {
return <el-tag key={index}>{item}</el-tag>
})}
</div>
)
}
}
</script>
<style>
.tag__wrap {
width: 200px;
display: flex;
justify-content: space-between;
}
</style>
複制代碼
jsx 擴充符
擴充符将一個對象的屬性智能的合并到元件optiion中
<script>
export default {
name: 'sync',
created() {
console.log(this)
},
data() {
return {
obj: {
class: ['sync__class1', 'sync__class2']
}
}
},
render() {
return <div {...this.obj} />
}
}
</script>
複制代碼
使用擴充符能友善我們插入多個class時,案例中發現如下報錯
提示我們這個對象以及observed檢視源碼如下,定義在src/core/vdom/create-element.js 中的_createElement函數中
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
複制代碼
這段代碼判斷data中中定義的對象以及data的__ob__,如果已經定義(代表已經被observed,上面綁定了Oberver對象)則提示
Avoid using observed data object ...
錯誤資訊。
經過分析後,改寫成
<script>
export default {
name: 'sync',
render() {
const obj = {
class: ['sync__class1', 'sync__class2']
}
return <div {...obj} />
}
}
</script>
複制代碼
jsx sync實作
<script>
export default {
name: 'sync',
render() {
return (
<div class="sync__click" onClick={this.onClick}>
<el-pagination
total={10}
current-page={this.index}
page-size={5}
{...{
on: {
'update:currentPage': value => {
this.index = value
console.log('頁數改變了')
}
}
}}
/>
</div>
)
},
data() {
return {
index: 1
}
},
methods: {
onClick() {
console.log('點選事件觸發了')
}
}
}
</script>
<style>
.sync__click {
width: 400px;
height: 500px;
background-color: #efefef;
}
</style>
複制代碼
總結
本文主要是認識了
高階元件和jsx的定義和基本的使用方
式。再這之前梳理了template生成的過程,底層都是
通過render函數
來實作的。接下來講了
render的相關語
法,以及書寫render會帶來維護的困難,不易讀懂的困擾。最後使用
jsx文法簡化render書寫
,并且
舉了幾個vue常用的指令,除了v-show其他的實作都要通過自己來實作
。當然jsx的知識點還有很多,在有了以上知識的基礎上,再去研究jsx的更多文法以及使用場景也會更加輕松些。
參考
hoc issues
babel-plugin-transform-vue-jsx
騰訊web
vue 官網
render 函數