組件系統是Vue.js其中一個重要的概念,它提供了一種抽象,讓我們可以使用獨立可復用的小組件來構建大型應用,任意類型的應用界面都可以抽象為一個組件樹。
一、四個核心組成
1、props 2、自定義事件 event 3、插槽及作用域插槽 slot 4、組件方法 method。
任何組件均離不開以上4點,我們在開發過程中,以這4點入手,封裝我們想要的組件。
以element-ui table組件為例
Table Attributes 指的是 props
props data 顯示的數據
Table Events 指的是 自定義事件
自定義事件 selection-change 當選擇項發生變化時會觸發該事件
Table Slot 插槽
append 插入至表格最后一行之后的內容,如果需要對表格的內容進行無限滾動操作,可能需要用到這個 slot。若表格有合計行,該 slot 會位于合計行之上。
Table-column Scoped Slot 插槽及作用域插槽 slot
Table-column 是 el-table-column組件,有一個默認插槽 自定義列的內容,參數為 { row, column, $index }
我們通常這樣使用
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
<el-button type="text" size="small">編輯</el-button>
</template>
</el-table-column>
slot-scope="scope" 父組件通過scope訪問子組件作用域。
Table Methods 指的是 組件方法
組件方法 clearSelection 用于多選表格,清空用戶的選擇 。 組件方法是通過 添加ref 索引,獲取組件實例后調用。this.$refs.組件ref標識.組件方法
以上是組件核心概念。
二、組件注冊
1、組件名
組件名應該始終是多個單詞的,根組件 App 除外
這樣做可以避免跟現有的以及未來的 HTML 元素相沖突,因為所有的 HTML 元素名稱都是單個單詞的。
單文件組件的文件名應該要么始終是單詞大寫開頭 (PascalCase),要么始終是橫線連接 (kebab-case)
混用文件命名方式有的時候會導致大小寫不敏感的文件系統的問題,這也是橫線連接命名同樣完全可取的原因
使用 kebab-case
當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如 <my-component-name>
Vue.component('my-component-name', { /* ... */ })
使用 PascalCase
當使用 PascalCase (駝峰式命名) 定義一個組件時,你在引用這個自定義元素時兩種命名法都可以使用。也就是說 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,盡管如此,直接在 DOM (即非字符串的模板) 中使用時只有 kebab-case 是有效的
Vue.component('MyComponentName', { /* ... */ })
2、全局注冊
以上方法都屬于全局注冊, 也就是說它們在注冊之后可以用在任何新創建的 Vue 根實例 (new Vue) 的模板中, 比如
HTML
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
JS
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
在所有子組件中也是如此,也就是說這三個組件在各自內部也都可以相互使用.
3、局部注冊
如果不需要全局注冊,或者是讓組件使用在其它組件內,可以用選項對象的 components 屬性實現局部注冊, 這里不做詳述。
三、父子組件通信
在vue組件通信中其中最常見通信方式就是父子組件之中的通信,而父子組件的設定方式在不同情況下又各有不同。最常見的就是父組件為控制組件子組件為視圖組件。父組件傳遞數據給子組件使用,遇到業務邏輯操作時子組件觸發父組件的自定義事件。無論哪種組織方式父子組件的通信方式都是大同小異
父組件到子組件通訊
父組件到子組件的通訊主要為:子組件接受使用父組件的數據,這里的數據包括屬性和方法(String, Number, Boolean, Object, Array, Function)。vue提倡單項數據流,因此在通常情況下都是父組件傳遞數據給子組件使用,子組件觸發父組件的事件,并傳遞給父組件所需要的參數
通過 props 傳遞數據 (推薦)
父子通訊中最常見的數據傳遞方式就是通過props傳遞數據,就好像方法的傳參一樣,父組件調用子組件并傳入數據,子組件接受到父組件傳遞的數據進行驗證使用
props 可以是數組或對象,用于接收來自父組件的數據。props 可以是簡單的數組,或者使用對象作為替代,對象允許配置高級選項,如類型檢測、自定義校驗和設置默認值
prop 的定義應該盡量詳細,至少需要指定其類型
<!-- 父組件 -->
<template>
<div>
<my-child :parentMessage="parentMessage"></my-child>
</div>
</template>
<script>
import MyChild from '@components/common/MyChild'
export default {
components: {
MyChild
},
data() {
return {
parentMessage: "我是來自父組件的消息"
}
}
}
</script>
<!-- 子組件 -->
<template>
<div>
<span>{{ parentMessage }}</span>
</div>
</template>
<script>
export default {
props: {
parentMessage: {
type: String,
default: '默認顯示的信息'
// require: true // 必填
}
}
}
</script>
子組件到父組件通訊
子組件到父組件的通訊主要為父組件如何接受子組件之中的數據。這里的數據包括屬性和方法(String, Number, Boolean, Object, Array, Function)
通過 $emit 傳遞父組件數據 (推薦)
與父組件到子組件通訊中的$on配套使用,可以向父組件中觸發的方法傳遞參數供父組件使用
<!-- 父組件 -->
<template>
<div>
<my-child @childEvent="parentMethod"></my-child>
</div>
</template>
<script>
import MyChild from '@components/common/MyChild'
export default {
components: {
MyChild
},
data() {
return {
parentMessage: '我是來自父組件的消息'
}
},
methods: {
parentMethod({ name, age }) {
console.log(this.parentMessage, name, age)
}
}
}
</script>
<!-- 子組件 -->
<template>
<div>
<h3>子組件</h3>
</div>
</template>
<script>
export default {
mounted() {
this.$emit('childEvent', { name: 'zhangsan', age: 10 })
}
}
</script>
不推薦的通信方式
this.children
this.$refs
四、兄弟組件通信
1、vuex
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,它是響應式的,狀態發生變化,組件會更新。
vuex實現兄弟組件通信非常簡單,組件A引用vuex數據,組件B通過方法改變vuex數據,vuex狀態是響應式的,數據放生變化,組件A會更新。
2、eventBus又稱為事件總線
在vue中可以使用eventBus來作為溝通橋梁, 就像是所有組件共用相同的事件中心,可以向該中心注冊發送事件或接收事件, 所有組件都可以通知其他組件。
初始化
首先需要創建一個事件總線并將其導出, 以便其他模塊可以使用或者監聽它。
我們在src/components/目錄下新建文件bus.js。
import Vue from 'vue'
export default new Vue()
發送事件
假設你有兩個兄弟組件: ComA和ComB,ComA發送消息給ComB。
ComA這樣
<template>
<div>
<button @click="sendMsg">給組件B發送消息</button>
</div>
</template>
<script>
import bus from './bus'
export default {
name: 'comA',
data () {
return {
}
},
methods: {
sendMsg() {
bus.$emit('fromA', {
phone: 13800138000
})
}
}
}
</script>
很顯然,ComA中使用bus.$emit(事件名,數據);向事件中心注冊發送事件。
接收事件
ComB接受ComA發送過來的消息。
<template>
<div>
<p>{{fromA}}</p>
</div>
</template>
<script>
import bus from './bus'
export default {
name: 'comB',
data () {
return {
fromA: '',
}
},
mounted() {
bus.$on('fromA', param => {
this.fromA = param.phone;
})
}
}
</script>
于是,當ComA發送了一個手機號碼phone給ComB時,comB就會接收并顯示。
父組件
在父組件中調用ComA和ComB兩個兄弟組件。
<template>
<div>
<comA></comA>
<comB></comB>
</div>
</template>
<script>
import ComA from './ComA.vue'
import ComB from './ComB.vue'
export default {
components: {
ComA,
ComB
},
}
</script>
五、多層級組件通訊
1、vuex
2、eventBus
3、provide / inject
provide 和 inject 主要在開發高階插件/組件庫時使用。并不推薦用于普通應用程序代碼中。
這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在其上下游關系成立的時間里始終生效。
// 父級組件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子組件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
六、作用域插槽
有時讓插槽內容能夠訪問子組件中才有的數據是很有用的。例如,設想一個帶有如下模板的 <current-user> 組件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
我們可能想換掉備用內容,用名而非姓來顯示。如下:
<current-user>
{{ user.firstName }}
</current-user>
然而上述代碼不會正常工作,因為只有 <current-user> 組件可以訪問到 user 而我們提供的內容是在父級渲染的。
為了讓 user 在父級的插槽內容中可用,我們可以將 user 作為 <slot> 元素的一個 attribute 綁定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
綁定在 <slot> 元素上的 attribute 被稱為插槽 prop。現在在父級作用域中,我們可以使用帶值的 v-slot 來定義我們提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
在這個例子中,我們選擇將包含所有插槽 prop 的對象命名為 slotProps,但你也可以使用任意你喜歡的名字。
七、組件的方法
組件方法,就是我們寫組件時,在methods選項里邊定義的一些方法,他通常是對數據的CURD。
element-ui我們常用的有
this.refs.[treeName].getCheckedKeys() tree 返回目前被選中的節點的 key 所組成的數組
八、vue 語法糖
1、v-model
v-model可以實現數據雙向的綁定,自動為組件添加了props 名為 value 和 自定義事件 名為 input。
<input type="text" v-model="name">
實際上,上面的代碼是下面代碼的語法糖。
<input v-bind:value="name" v-on:input="name=$event.target.value"/>
自定義組件的 v-model
一個組件上的 v-model 默認會利用名為 value 的 prop 和名為 input 的事件,但是像單選框、復選框等類型的輸入控件可能會將 value attribute 用于不同的目的。model 選項可以用來避免這樣的沖突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
現在在這個組件上使用 v-model 的時候:
<base-checkbox v-model="lovingVue"></base-checkbox>
這里的 lovingVue 的值將會傳入這個名為 checked 的 prop。同時當 <base-checkbox> 觸發一個 change 事件并附帶一個新的值的時候,這個 lovingVue 的 property 將會被更新。
2、.sync 修飾符
在有些情況下,我們可能需要對一個 prop 進行“雙向綁定”。不幸的是,真正的雙向綁定會帶來維護上的問題,因為子組件可以變更父組件,且在父組件和子組件都沒有明顯的變更來源。
這也是為什么我們推薦以 update:myPropName 的模式觸發事件取而代之。舉個例子,在一個包含 title prop 的假設的組件中,我們可以用以下方法表達對其賦新值的意圖:
this.$emit('update:title', newTitle)
然后父組件可以監聽那個事件并根據需要更新一個本地的數據 property。例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
為了方便起見,我們為這種模式提供一個縮寫,即 .sync 修飾符:
<text-document v-bind:title.sync="doc.title"></text-document>
九、組件選項推薦順序
<script>
export default {
el: '#app', // 只在由 new 創建的實例中遵守。
// 全局感知
name: 'name', // 組件name
parent: VueInstance, // 指定父實例
// 組件類型
functional: false, // 沒有data 實例 沒有上下文
// 模板修改器
delimiters: ['${', '}'], // 分隔符變成了 ES6 模板字符串的風格
comments: false, // 當設為 true 時,將會保留且渲染模板中的 HTML 注釋。默認行為是舍棄它們。
// 模板依賴
components: {}, // 子組件
directives: {}, // 自定義指令
filters: {}, // 自定義過濾器
// 組合
extends: CompA, // 擴展另一個組件 和 mixins 類似
mixins: [tableEvents], // 混入選項對象, 混入實例對象可以像正常的實例對象一樣
// 接口
inheritAttrs: true,
model: { // 自定義組件在使用 v-model 時定制 prop 和 event
prop: 'checked',
event: 'change'
},
propsData: { // 只用于 new 創建的實例中。 創建實例時傳遞 props。
},
// 本地狀態
data: () => ({ // 本地狀態
}),
computed: { // 計算屬性
},
// 事件 生命周期鉤子
watch: {
},
// 生命周期鉤子
beforeCreate() {
},
created() {
},
beforeMount() {
},
mounted() {
},
beforeUpdate() {
},
updated() {
},
activated() {
},
deactivated() {
},
beforeDestroy() {
},
destroyed() {
},
// 非響應式的屬性 (不依賴響應系統的實例屬性)
methods() {
},
// 渲染 (組件輸出的聲明式描述)
template: '<div>demo</div>', // 渲染模板
render: function (createElement) {
return createElement(
'h' + this.level, // 標簽名稱
this.$slots.default // 子節點數組
)
},
renderError (h, err) { // 只在開發者環境下工作。
return h('pre', { style: { color: 'red' }}, err.stack)
}
}
</script>
十、組件樣式推薦使用CSS 作用域
<style scoped>
</style>