在學習瞭解組件複用的時候查看資料,看到了mixins,extend
方法同時也查到了高級函數(Higher-Order Components)和jsx
等字眼。聽上去hoc和jsx有種高級的感受,那得好好研究下。css
高階組件定義 : 高階組件是一個方法,這個方法接收一個原始組件做爲參數,並返回新的組件。html
jsx定義 : JSX 是一種相似於 XML 的 JavaScript 語法擴展 JSX 不是由引擎或瀏覽器實現的。相反,咱們將使用像 Babel 這樣的轉換器將 JSX 轉換爲常規 JavaScript。基本上,JSX 容許咱們在 JavaScript 中使用相似 HTML 的語法。vue
Vue 推薦使用在絕大多數狀況下使用 template 來建立你的 HTML。然而在一些場景中,你真的須要 JavaScript 的徹底編程的能力,這就是 render 函數,它比 template 更接近編譯器。node
其實咱們傳入template模板以後,在vue底層也會將其轉換成render函數
vue源碼 src/platform/web/entry-runtime-with-compiler.js文件中代碼以下,咱們只須要關注tempalte變量
,首先判斷配置項中有無傳入render,若是沒有也會有不少種狀況,不管哪一種狀況都會給template變量賦值一個DOM字符串
,最終經過compileToFunctions函數將其轉換成render函數
,經過結構賦值的方式賦值給render變量
,而後添加到實例上 具體能夠看這篇文章git
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
}
}
複製代碼
使用以前須要的配置和使用語法這裏咱們主要來看props 、class、on(事件)、slots
這四個屬性,首先來看代碼github
// 子組件
<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 lang='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標籤
。web
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節點element-ui
一樣來看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
的父組件事件
首先咱們來看下用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>
)
}
}
複製代碼
<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 lang='scss' scoped>
.input__wrap {
width: 200px;
}
</style>
複製代碼
<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>
複製代碼
擴展符將一個對象的屬性智能的合併到組件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>
複製代碼
<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的更多語法以及使用場景也會更加輕鬆些。