【Vue】詳解元件的基礎與高階用法
構建元件
元件基礎
一個元件由 template、data、computed、methods等選項組成。需要注意:
- template 的 DOM 結構必須有根元素
- data 必須是函式,資料通過 return 返回出去
// 示例:定義一個元件 MyComponent
var MyComponent = {{
data: function () {
return {
// 資料
}
},
template: '<div>元件內容</div>'
}
由於 HTML 特性不區分大小寫, 在使用kebab-case
(小寫短橫線分隔命名) 定義元件時,引用也需要使用這個格式如 <my-component>
PascalCase
(駝峰式命名) 定義元件時<my-component>
和<MyComponent>
這兩種格式都可以引用。
.vue 單檔案元件
如果專案中使用打包編譯工具 webpack,那引入 vue-loader 就可以使用 .vue
字尾檔案構建元件。一個.vue
單檔案元件 (SFC) 示例:
// MyComponent.vue 檔案 <template> <div>元件內容</div> </template> <script> export default { data () { return { // 資料 } } } </script> <style scoped> div{ color: red } </style>
.vue
檔案使元件結構變得清晰,使用.vue
還需要安裝 vue-style-loader 等載入器並配置 webpack.config.js 來支援對 .vue 檔案及 ES6 語法的解析。
註冊元件
手動註冊
元件定義完後,還需要註冊才可以使用,註冊分為全域性和區域性註冊:
// 全域性註冊,任何 Vue 例項都可引用 Vue.component('my-component', MyComponent) // 區域性註冊,在註冊例項的作用域下有效 var MyComponent = { /* ... */ } new Vue({ components: { 'my-component': MyComponent } }) // 區域性註冊,使用模組系統,元件定義在統一資料夾中 import MyComponent from './MyComponent.vue' export default { components: { MyComponent // ES6 語法,相當於 MyComponent: MyComponent } }
注意全域性註冊的行為必須在根 Vue 例項 (通過 new Vue) 建立之前發生。
自動註冊
對於通用模組使用列舉的註冊方式程式碼會非常不方便,推薦使用自動化的全域性註冊。如果專案使用 webpack,就可以使用其中的require.context
一次性引入元件資料夾下所有的元件:
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst' // 使用 lodash 進行字串處理
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
'./components', // 其元件目錄的相對路徑
false, // 是否查詢其子目錄
/Base[A-Z]\w+\.(vue|js)$/ // 匹配基礎元件檔名的正則表示式
)
requireComponent.keys().forEach(fileName => {
// 獲取元件配置
const componentConfig = requireComponent(fileName)
// 獲取元件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剝去檔名開頭的 `./` 和結尾的副檔名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)
// 全域性註冊元件
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
元件通訊
父單向子的 props
Vue 2.x 以後父元件用props
向子元件傳遞資料,這種傳遞是單向/正向的,反之不能。這種設計是為了避免子元件無意間修改父元件的狀態。
子元件需要選項props
宣告從父元件接收的資料,props
可以是字串陣列和物件,一個 .vue 單檔案元件示例如下
// ChildComponent.vue
<template>
<div>
<b>子元件:</b>{{message}}
</div>
</template>
<script>
export default {
name: "ChildComponent",
props: ['message']
}
</script>
父元件可直接傳單個數據值,也可以可以使用指令v-bind
動態繫結資料:
// parentComponent.vue
<template>
<div>
<h1>父元件</h1>
<ChildComponent message="父元件向子元件傳遞的非動態值"></ChildComponent>
<input type="text" v-model="parentMassage"/>
<ChildComponent :message="parentMassage"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from '@/components/ChildComponent'
export default {
components: {
ChildComponent
},
data () {
return {
parentMassage: ''
}
}
}
</script>
配置路由後執行效果如下:
子向父的 $emit
當子元件向父元件傳遞資料時,就要用到自定義事件。子元件中使用 $emit()
觸發自定義事件,父元件使用$on()
監聽,類似觀察者模式。
子元件$emit()
使用示例如下:
// ChildComponent.vue
<template>
<div>
<b>子元件:</b><button @click="handleIncrease">傳遞數值給父元件</button>
</div>
</template>
<script>
export default {
name: "ChildComponent",
methods: {
handleIncrease () {
this.$emit('increase',5)
}
}
}
</script>
父元件監聽自定義事件 increase,並做出響應的示例:
// parentComponent.vue
<template>
<div>
<h1>父元件</h1>
<p>數值:{{total}}</p>
<ChildComponent @increase="getTotal"></ChildComponent>
</div>
</template>
<script>
import ChildComponent from '@/components/ChildComponent'
export default {
components: {
ChildComponent
},
data () {
return {
total: 0
}
},
methods: {
getTotal (count) {
this.total = count
}
}
}
</script>
訪問 parentComponent.vue 頁面,點選按鈕後子元件將數值傳遞給父元件:
子孫的鏈與索引
元件的關係有很多時跨級的,這些元件的呼叫形成多個父鏈與子鏈。父元件可以通過this.$children
訪問它所有的子元件,可無限遞歸向下訪問至最內層的元件,同理子元件可以通過this.$parent
訪問父元件,可無限遞歸向上訪問直到根例項。
以下是子元件通過父鏈傳值的部分示例程式碼:
// parentComponent.vue
<template>
<div>
<p>{{message}}</p>
<ChildComponent></ChildComponent>
</div>
</template>
// ChildComponent.vue
<template>
<div>
<b>子元件:</b><button @click="handleChange">通過父鏈直接修改資料</button>
</div>
</template>
<script>
export default {
name: "ChildComponent",
methods: {
handleChange () {
this.$parent.message = '來自 ChildComponent 的內容'
}
}
}
</script>
顯然點選父元件頁面的按鈕後會收到子元件傳過來的 message。
在業務中應儘量避免使用父鏈或子鏈,因為這種資料依賴會使父子元件緊耦合,一個元件可能被其他元件任意修改顯然是不好的,所以元件通訊常用props
和$emit
。
遞迴元件
元件可以在自己的 template 模板中呼叫自己,需要設定 name 選擇。
// 遞迴元件 ComponentRecursion.vue
<template>
<div>
<p>遞迴元件</p>
<ComponentRecursion :count="count + 1" v-if="count < 3"></ComponentRecursion>
</div>
</template>
<script>
export default {
name: "ComponentRecursion",
props: {
count: {
type: Number,
default: 1
}
}
}
</script>
如果遞迴元件沒有 count 等限制數量,就會丟擲錯誤(Uncaught RangeError: Maximum call stack size exceeded)。
父頁面使用該遞迴元件,在 Chrome 中的 Vue Devtools 可以看到元件遞迴了三次:
遞迴元件可以開發未知層級關係的獨立元件,如級聯選擇器和樹形控制元件等。
動態元件
如果將一個 Vue 元件命名為 Component 會報錯(Do not use built-in or reserved HTML elements as component id: Component),因為 Vue 提供了特殊的元素 <component>
來動態掛載不同的元件,並使用 is
特性來選擇要掛載的元件。
以下是使用<component>
動態掛載不同元件的示例:
// parentComponent.vue
<template>
<div>
<h1>父元件</h1>
<component :is="currentView"></component>
<button @click = "changeToViewB">切換到B檢視</button>
</div>
</template>
<script>
import ComponentA from '@/components/ComponentA'
import ComponentB from '@/components/ComponentB'
export default {
components: {
ComponentA,
ComponentB
},
data() {
return {
currentView: ComponentA // 預設顯示元件 A
}
},
methods: {
changeToViewB () {
this.currentView = ComponentB // 切換到元件 B
}
}
}
</script>
改變 this.currentView
的值就可以自由切換 AB 元件:
與之類似的是vue-router
的實現原理,前端路由到不同的韻實際上就是載入不同的元件。