by yugasun from yugasun.com/post/you-ma…
本文可全文轉載,但需要保留原作者和出處。
初識元件
元件(Component)絕對是 Vue 最強大的功能之一。它可以擴充套件HTML元素,封裝可複用程式碼。從較高層面講,可以理解元件為自定義的HTML元素,Vue 的編譯器為它新增了特殊強大的功能。所有的 Vue 元件同時也都是 Vue 的例項,因此可以接受相同的選項物件(除了一些特有的選項)並提供相同的生命週期函式。
再來回顧下 你也許不知道的Vuejs – 花式渲染目標元素 中的程式碼:
<div id="app1">
<helloworld/>
</div>
複製程式碼
Vue.component("helloworld", {
template: "<h1>{{ msg }}</h1>",
data () {
return {
msg: "Hello Vue.js!"
}
}
})
var app1 = new Vue({
el: "#app1"
})
複製程式碼
上面通過 Vue.component
註冊了一個全域性元件,然後在 div#app
元素內通過 <helloworld/>
標籤直接使用。可以看出,這裡就是相當於自定義了一個 HTML 元素 helloworld
,它的功能就是輸出一個內容為 msg
的 h1
標籤。這就是一個基本全域性元件的定義方式,當然你也可以註冊為區域性元件:
var app2 = new Vue({
el: "#app2",
components: {
`helloworld`: {
template: "<h1>{{ msg }}</h1>",
data () {
return {
msg: "Hello Vue.js!"
}
}
}
}
})
複製程式碼
無論是全域性或者區域性註冊元件,它跟上一篇中的指令註冊是非常相似的。區域性註冊元件就是在建立 Vue 例項的時候新增一個 components
物件屬性,它的鍵值對就是一個自定義元件,鍵是元件名,值是建立組建的配置物件引數。當然也可以將元件定義放到單獨的檔案,然後通過引入的方式,然後新增到components屬性中,這個在單檔案元件中會具體講到。
元件間通訊
既然說到元件,就不得不說元件間通訊了,實際開發中,我們經常需要在不同元件間傳遞/共享資料,所以實現元件間通訊是非常重要的。
元件間關係可以總結為 父子元件
和 非父子元件
,自然通訊方式也就是這兩種了。
父子元件間通訊
如上圖,在 Vue 中,父子元件的關係可以總結為 props向下傳遞,事件向上傳遞
。也就是父元件通過 prop
給子元件下發資料,子元件通過 $emit 事件, 給父元件傳送資料。先來看個例子:
<div id="app3">
當前輸入內容:{{ text }}<br>
<com-input :text="text" v-on:change="handleChange"/>
</div>
複製程式碼
Vue.component(`com-input`, {
props: {
text: {
type: String,
default: "請輸入"
}
},
template: `<input v-on:change="handleChange" v-model="msg"/>`,
data () {
return {
// 這裡定義為 input 的 v-model繫結值
msg: this.text
}
},
methods: {
// 當input值變化時,執行函式,通過 $emit change 事件,
// 父級元件通過 v-on:change 來監聽此事件,執行相關操作
handleChange (e) {
this.$emit(`change`, this.msg)
}
}
})
var app3 = new Vue({
el: "#app3",
data () {
return {
text: `Hello Vue.js`
}
},
methods: {
handleChange (val) {
this.text = val
}
}
})
複製程式碼
原理解析:上面的程式碼中,先通過
Vue.component
定義了com-input
元件,給它新增了props
屬性,用來接收父級通過屬性傳遞的屬性資料text
,這裡text
是個物件,含有type - 屬性值型別
和default - 預設值
兩個屬性。當然props
也可以為所有從父級接受的屬性陣列,有關props
基礎知識請直接閱讀 官方文件。然後將初始值賦值給了 data 中的msg
,該子元件的模板是個input
,通過v-model
實現input的值
和msg
的雙向繫結,當input值變化時,通過this.$emit(`change`, this.msg)
,發出change
事件,同時將當前值作為監聽器回撥引數,這樣父級元件就可以通過v-on:change
來監聽此事件,獲取修改後的值,執行相關操作了。
雖然這段程式碼同時實現了上述圖片中的 父 -> 子
和 子 -> 父
通訊流程,但是程式碼還是比較繁瑣的,單純實現單個資料的迴圈傳遞,就需要父子元件同時監聽改變事件,執行監聽回撥函式,是不是太麻煩了。要是能直接修改 props
中的 text
值就好了,實踐證明,這是不行的,因為直接修改,會報下面錯誤(注意只有引入 vue.js
檔案才會出現,因為 vue.min.js
檔案移除了 [Vue warn]
錯誤提示功能):
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop`s value. Prop being mutated: “text”
這個問題,Vue 作者早就想到了,那就是使用 .sync 修飾符。早在 1.x
版本中此功能是一直存在的,但是作者認為它破壞了 單向資料流 的原則,所以 2.0
釋出後,就移除了該修飾符,但是後來發現在實際開發中,有很多相關需求, 於是在 2.3.0+
版本後,又重新引入了 .sync
修飾符,不過內部實現是跟 1.x
版本有區別的,它並沒有破壞 單向資料流 原則,實際上內部就是幫我們實現了父級元件監聽和修改相關屬性值的操作。
使用 .sync
修改後的程式碼如下:
<div id="app4">
當前輸入內容:{{ text }}<br>
<com-input2 v-bind:text.sync="text"/>
</div>
複製程式碼
Vue.component(`com-input2`, {
props: {
text: {
type: String,
default: "請輸入"
}
},
template: `<input v-on:change="handleChange" v-model="msg"/>`,
data () {
return {
msg: this.text
}
},
methods: {
handleChange (e) {
this.$emit(`update:text`, this.msg)
}
}
})
var app4 = new Vue({
el: "#app4",
data () {
return {
text: `Hello Vue.js`
}
}
})
複製程式碼
這次我們只是將子元件的 $emit
事件名修改為 update:text
,並刪除了父級元件 v-on:change
監聽和相關監聽回撥,並在模板中 v-bind:text
後面新增了 .sync
修飾符,這樣就是實現了相同的功能,程式碼確實精簡了很多。實際上 Vue 在編譯含有 .sync
修飾符的 v-bind
指令時,會自動實現監聽 update
事件的相關程式碼,也就是:
<com-input2 v-bind:text.sync="text"/>
複製程式碼
會被擴充套件為:
<com-input2 v-bind:text="text" v-on:update="val => text = val"/>
複製程式碼
注意:
val => text = val
是箭頭函式,關於箭頭函式的介紹可以看這裡:箭頭函式
這樣一解析就很好理解了,全部是我們上一節講到的內容。
非父子元件間通訊
如果是兩個非父子元件,並且有共同的父級元件,那麼它拆解為 子 -> 父 -> 子
的過程,這個就完全可以使用 父子元件間通訊
方法實現。如果是多個元件或者不同父元件的元件間通訊,這時我們可以藉助建立空的 Vue 例項作為事件匯流排,通過 釋出訂閱模式
進行資料傳遞。 程式碼如下:
<div id="app5">
元件A: <com-a></com-a><br>
元件B: <com-b></com-b>
</div>
複製程式碼
var bus = new Vue()
Vue.component(`com-a`, {
template: `<input v-on:change="handleChange" v-model="msg"/>`,
data () {
return {
msg: `Hello Vue.js`
}
},
methods: {
handleChange() {
bus.$emit(`a-change`, this.msg)
}
},
created () {
var me = this
bus.$on(`b-change`, function (msg) {
me.msg = msg
})
}
})
Vue.component(`com-b`, {
template: `<input v-on:change="handleChange" v-model="msg"/>`,
data () {
return {
msg: `Hello Vue.js`
}
},
methods: {
handleChange() {
bus.$emit(`b-change`, this.msg)
}
},
created () {
var me = this
bus.$on(`a-change`, function (msg) {
me.msg = msg
})
}
})
var app5 = new Vue({
el: `#app5`
})
複製程式碼
熟悉 釋出訂閱模式
的同學,應該很容易理解上面這段程式碼,建立的全域性空 Vue 例項 bus
就是用來充當中央事件匯流排,所有的事件都經過它來觸發和傳播。
思路解析:在元件
com-a
中,當input
值發生改變時,通過bus.$emit(`a-change`, this.msg)
來觸發修改事件,並將其更新後的值做為引數傳遞,元件com-b
通過bus.$on(`a-change`, xxx)
來監聽,進行值更新操作,元件com-b
也是相同原理。
當然在複雜情況下,我們應該考慮使用專門的 狀態管理模式,比如 vuex,這個將在後續的文章中講到。
動態元件
Vue 中還提供了 component
元素,允許我們在實際開發中,通過修改其 is
屬性值,來動態切換元件。這個在某些應用場景非常實用,筆者曾經有個需求就是,需要根據引數 type
來繪製不同型別的圖表,而我的所有圖表型別都已經裝成了一個獨立的元件,所以我只需要依據此特性,通過引數 type
來動態修改元素 component
的屬性 is
為對應的元件名稱即可。
下面來看示例程式碼:
<div id="app6">
<button v-on:click="changeType">改變元件</button><br>
<component v-bind:is="currentComp"></component>
</div>
複製程式碼
var app6 = new Vue({
el: `#app6`,
data () {
return {
type: `a`
}
},
computed: {
currentComp () {
return this.type === `a` ? `com-a` : `com-b`;
}
},
components: {
`com-a`: {
template: `<h1>我是元件a</h1>`
},
`com-b`: {
template: `<h1>我是元件b</h1>`
}
},
methods: {
changeType () {
this.type = this.type === `a` ? `b` : `a`;
}
}
})
複製程式碼
執行上面程式碼,點選改變元件按鈕,就可以輕鬆的實現元件 com-a
和 com-b
的動態切換了,是不是很酷?趕緊動手嘗試下吧。
總結
元件作為 Vue 中最強大的功能之一,其特性當然不止上面所提到的,感興趣的同學可以到官方文件中閱讀了解。學會封裝可重用的程式碼,增加程式碼可複用性,是個需要長期學習和總結的過程,這個就需要我們在不斷的專案開發中嘗試和總結。本節也是關於 Vue 基礎知識的最後一節,當然還有很多 Vue 的基礎知識,文章中並未提到,因為官方文件已經非常詳細了,篇幅有限,也不再一一介紹。請至少將官方文件仔細閱讀一篇,以便理解後面的專案實戰開發。從下一篇開始,我將進行實際專案開發講解,盡請期待~