上篇文章我們提到了組件的概念,組件是目前模塊化、組件化開發模式中必不可少的單元形式,那么除了其概念和可復用性外,我們對它的職能劃分了解多少呢?
本文將立足 Vue 組件的職能來談談我個人對于其劃分的理解,唯有了解不同類型組件的職能才能編寫出可維護、低耦合的前端代碼。
## 組件的職能劃分
如果要將 Vue 組件按照職能劃分,我們可以將其分為兩種類型:容器組件和展示組件。
容器組件和展示組件的概念來自于 `Redux` 文檔,那么首先什么是容器組件呢?顧名思義,它是一個容器性質的組件,我們可以把它理解為最外層的父組件,也就是最頂層的組件,一般我們把它放置在 `views` 文件夾下,其功能主要用于做數據提取與實現公共邏輯,然后渲染對應的子組件。
另一類組件叫做展示組件,字面意思就是主要用于做展示的組件,其主要功能是負責接收從容器組件傳輸過來的數據并在頁面上渲染,實現其內部獨有的功能邏輯。
一個頁面中容器組件與展示組件的關系如下圖所示:

上圖我們以博客首頁為例,容器組件就是整個首頁最外層的父組件,而展示組件就包含了導航欄、文章列表、底部等子組件,代碼層面如下:
```
<template>
<div>
<navigation @count="countFn"></navigation>
<article :list="articleList"></article>
<foot></foot>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
export default {
mounted() {
this.SET_BLOG_DATA(); // 調用接口獲取數據
},
computed: {
...mapGetters(['articleList']), // 監聽 state
}
methods: {
...mapActions(['SET_BLOG_DATA', 'SET_NAV_COUNT']),
countFn(item) {
// 調用接口存儲導航點擊次數并跳轉,通過派發 action 的形式來發起 state 變化
this.SET_NAV_COUNT({ type: item.type });
this.$router.push({name: item.route});
}
}
}
</script>
```
以上是首頁容器組件中的主要代碼,其主要做了兩件事情:數據的傳遞和回調的處理,當然還可以包括處理一些該頁面中不屬于任何一個展示組件的方法,比如校驗登錄狀態。在一個容器組件中可以包含多個展示組件,下面我們來看一下展示組件 `Navigation` 中的代碼:
```
<template>
<ul>
<li
v-for="(item, index) in nav"
:key="index"
@click="goNav(item)"
v-text="item.name"
></li>
</ul>
</template>
<script>
export default {
data() {
return {
nav: [{
name: '首頁',
route: 'index',
type: 'index'
}, {
name: '文章',
route: 'article',
type: 'article'
}, {
name: '關于',
route: 'about',
type: 'about'
}]
}
},
methods: {
goNav(item) {
this.$emit('count', item); // 觸發回調
}
}
}
</script>
```
`Navigation` 導航組件只負責自己內部的數據渲染和回調邏輯,對于存儲每個導航的點擊量及跳轉邏輯來說,作為展示組件這并不是其所關心的,所以我們需要通過觸發容器組件回調的方式來實現。再來看一下展示組件 `Article` 的代碼:
```
<template>
<ul>
<li
v-for="(item, index) in list"
:key="index"
@click="goPage(item.id)"
v-text="item.title"
></li>
</ul>
</template>
<script>
export default {
props: {
// 接收容器組件數據
list: {
default: [],
type: Array
}
}
}
</script>
```
展示組件 Article 中動態的數據通過 `props` 從父組件中獲取,其內部只處理文章列表的渲染工作,這樣很好的將 UI 層面和應用層面進行了分離,便于今后該組件的復用。
此外 `Foot` 組件為純靜態組件,其只負責內部數據的渲染,不接收外部的數據和回調方法,這里就不做介紹了。
從以上代碼示例中我們不難發現容器組件和展示組件的主要區別和注意點:
展示組件
容器組件
作用
描述如何展現(骨架、樣式)
描述如何運行(數據獲取、狀態更新)
是否使用 Vuex
否
是
數據來源
props
監聽 Vuex state
數據修改
從 props 調用回調函數
向 Vuex 派發 actions
相比較如果上述的博客首頁不做組件的劃分,全部邏輯都放在一個組件中,那么必然會導致代碼的臃腫和難以維護,而一旦劃分了容器組件和展示組件,后期如果哪個頁面同樣需要展示文章列表,我們只需要傳遞不同的數據直接復用即可。
## 組件的層次結構
了解了組件職能的劃分后,我們再來看一下組件的層次結構。關于組件的層次,一般頁面中不宜嵌套超過 3 層的組件,因為超過 3 層后父子組件的通信就會變得相對困難,不利于項目的開發和維護。3 層結構的容器組件與展示組件的數據傳遞如下:

可見組件的層次越深數據傳遞的過程就會變得越復雜,當然這取決于你如何劃分容器組件和展示組件,比如我們可以將上述博客首頁換一種劃分方式:

上圖我們頁面中存在 3 個容器組件,每個容器組件又可以包含各自的展示組件,這樣一定程度上可以減少組件的層次嵌套深度。當然展示組件中也可以包含對應的容器組件來解決數據傳輸的問題:

這樣展示組件 B 下面的容器組件 C 便可以不依賴于容器組件 A 的數據,其可以單獨的進行數據獲取和狀態更新。
而對于那些你不知道應該劃分為容器組件和展示組件的組件,比如一些耦合度較高的組件,那么你可以暫時歸類到其他組件中,混用容器和展示,隨著日后功能的逐漸清晰,我們再將其進行劃分。
## 結語
本文主要介紹了容器組件和展示組件的概念和層次劃分,在編碼上,容器組件和展示組件各司其職,它們將容器和展示更好的分離,提高了組件的重用度,降低了功能上的耦合度,為高效、高質量的代碼開發奠定了基礎。
## 思考 & 作業
* 如果你了解 React,那么試想一下在 React 中展示組件與容器組件有哪些異同點?
* 如果需要你對掘金首頁進行組件的劃分,你會如何劃分其結構和層次?
* 在子組件的 `props` 中,如何動態的設置默認值?
- 開篇:Vue CLI 3 項目構建基礎
- 構建基礎篇 1:你需要了解的包管理工具與配置項
- 構建基礎篇 2:webpack 在 CLI 3 中的應用
- 構建基礎篇 3:env 文件與環境設置
- 構建實戰篇 1:單頁應用的基本配置
- 構建實戰篇 2:使用 pages 構建多頁應用
- 構建實戰篇 3:多頁路由與模板解析
- 構建實戰篇 4:項目整合與優化
- 開發指南篇 1:從編碼技巧與規范開始
- 開發指南篇 2:學會編寫可復用性模塊
- 開發指南篇 3:合理劃分容器組件與展示組件
- 開發指南篇 4:數據驅動與拼圖游戲
- 開發指南篇 5:Vue API 盲點解析
- 開發拓展篇 1:擴充你的開發工具
- 開發拓展篇 2:將 UI 界面交給第三方庫
- 開發拓展篇 3:嘗試使用外部數據
- 總結篇:寫在最后