在生活中,重復的機械勞動會消耗我們的時間和精力,提高生產成本,降低工作效率。同樣,在代碼世界中,編寫重復的代碼會導致代碼的冗余,頁面性能的下降以及后期維護成本的增加。由此可見將重復的事情復用起來是提高生產效率、降低維護成本的不二之選。
在 Vue 項目中,每一個頁面都可以看作是由大大小小的模塊構成的,即便是一行代碼、一個函數、一個組件都可以看作是一個個自由的模塊。那么提高代碼的復用性的關鍵便在于編寫可復用的模塊,也就是編寫可復用的代碼、函數和組件等。
## 一個簡單的例子
```
let person = [];
for (let i = 0; i < data.obj.items.length; i++) {
person.push({
name: data.obj.items[i].name,
age: data.obj.items[i].age
});
}
```
不知道上方代碼給你的第一印象是什么?總之給我的印象是糟糕的,因為出現了重復性的代碼片段 `data.obj.items`,可能這樣的代碼在我們團隊開發中隨處可見,這也說明了重復編碼現象其實無處不在。
面對自己編寫的代碼,我們應該保持一顆去重的心,發現重復的地方就相當于找到了可以復用的模塊。在不復用的情況下,上述代碼一旦需要修改變量 `items` 為 `lists`,那么我們就得修改 3 處地方,不知不覺就增加了維護成本。而到時候往往修改你代碼的人并不是你自己,所以對自己好點,對他人也會好點。復用后的代碼如下:
```
let person = [];
let values = data.obj.items;
for (let i = 0; i < values.length; i++) {
person.push({
name: values[i].name,
age: values[i].age
});
}
```
我們通過將 data.obj.items 的值賦值給變量 values 來實現了復用,此時修改 `items` 為 `lists` 的話我們只需修改一處地方即可,不管是維護成本還是代碼可讀性上,復用的優勢都顯而易見。
## 封裝成一個函數
除了使用變量的賦值緩存使用來解決數據的重復讀取外,我們在開發過程中重復性更多的也許是功能點的重復,比如:
```
<tempalte>
<div>
<input type="text" v-model="str1">
<input type="text" v-model="str2">
<div>{{ str1.slice(1).toUpperCase() }}</div>
<div>{{ str2.slice(1).toUpperCase() }}</div>
</div>
</template>
```
上述代碼的重復功能點在于截取輸入框中第二個字符開始到最后的值并把它們轉化成大寫字母,像這樣很簡單的操作雖然重復使用也不會出現太大的問題,但是如果是代碼量較多的操作呢?重復書寫相同功能的代碼是一種不經過大腦思考的行為,我們需要對其進行優化,這里我們可以把功能點封裝成一個函數:
```
export default {
methods: {
sliceUpperCase(val) {
return val.slice(1).toUpperCase()
}
}
}
```
如此我們只要在用到該方法的地方調用即可,將值傳入其中并返回新值。當然像在雙花括號插值和 v-bind 表達式中重復的功能點我們可以封裝成過濾器比較合適:
```
// 單文件組件注冊過濾器
filters: {
sliceUpperCase(val) {
return val.slice(1).toUpperCase()
}
}
// 全局注冊過濾器
Vue.filter('sliceUpperCase', function (val) {
return val.slice(1).toUpperCase()
})
```
然后在 html 中使用“管道”符進行過濾:
```
<div>{{ str1 | toUpperCase }}</div>
<div>{{ str2 | toUpperCase }}</div>
```
這樣我們就把重復的功能性代碼封裝成了函數,而不管是過濾器還是正常的方法封裝,其本質都是函數的封裝。
## 封裝成一個組件
相比較于函數的封裝,規模更大一點的便是組件的封裝,組件包含了模板、腳本以及樣式的代碼,在實際開發中組件的使用頻率也是非常大的,我們項目中的每一個頁面其實都可以看作是一個父組件,其可以包含很多子組件,子組件通過接收父組件的值來渲染頁面,父組件通過響應子組件的回調來觸發事件。
封裝一個組件主要包含兩種方式,一種是最常見的整體封裝,用戶通過改變數據源來呈現不同的頁面狀態,代碼結構不可定制化。例如:
```
<div>
<my-component data="我是父組件傳入子組件的數據"></my-component>
</div>
```
另一種便是自定義封裝,也就是插槽(slot),我們可以開放一部分槽位給父組件,使其能夠進行一定程度的定制化,例如:
```
<div>
<my-component data="我是父組件傳入子組件的數據">
<template slot="customize">
<span>這是定制化的數據</span>
</template>
</my-component>
</div>
```
在 myComponent 組件中我們便可以接收對應的 slot:
```
<div class="container">
<span>{{ data }}</span>
<slot name="customize"></slot>
<div>
```
這里我們通過定義 slot 標簽的 name 值為 customize 來接收父組件在使用該組件時在 template 標簽上定義的 slot="customize" 中的代碼,不同父組件可以定制不同的 slot 代碼來實現差異化的插槽。最終渲染出來的代碼如下:
```
<div>
<div class="container">
<span>我是父組件傳入子組件的數據</span>
<span>這是定制化的數據</span>
</div>
</div>
```
這樣我們就完成了一個小型組件的封裝,將共用代碼封裝到組件中去,頁面需要引入的時候直接使用 import 并進行相應注冊即可,當然你也可以進行全局的引入:
```
import myComponent from '../myComponent.vue'
// 全局
Vue.component('my-component', myComponent)
```
## 封裝成一個插件
在某些情況下,我們封裝的內容可能不需要使用者對其內部代碼結構進行了解,其只需要熟悉我們提供出來的相應方法和 api 即可,這需要我們更系統性的將公用部分邏輯封裝成插件,來為項目添加全局功能,比如常見的 loading 功能、彈框功能等。
Vue 提供給了我們一個 install 方法來編寫插件,使用該方法中的第一個 Vue 構造器參數可以為項目添加全局方法、資源、選項等。比如我們可以給組件添加一個簡單的全局調用方法來實現插件的編寫:
```
/* toast.js */
import ToastComponent from './toast.vue' // 引入組件
let $vm
export default {
install(Vue, options) {
// 判斷實例是否存在
if (!$vm) {
const ToastPlugin = Vue.extend(ToastComponent); // 創建一個“擴展實例構造器”
// 創建 $vm 實例
$vm = new ToastPlugin({
el: document.createElement('div') // 聲明掛載元素
});
document.body.appendChild($vm.$el); // 把 toast 組件的 DOM 添加到 body 里
}
// 給 toast 設置自定義文案和時間
let toast = (text, duration) => {
$vm.text = text;
$vm.duration = duration;
// 在指定 duration 之后讓 toast 消失
setTimeout(() => {
$vm.isShow = false;
}, $vm.duration);
}
// 判斷 Vue.$toast 是否存在
if (!Vue.$toast) {
Vue.$toast = toast;
}
Vue.prototype.$toast = Vue.$toast; // 全局添加 $toast 事件
}
}
```

成功編寫完插件的 JS 腳本后,我們在入口文件中需要通過 Vue.use() 來注冊一下該插件:
```
import Toast from '@/widgets/toast/toast.js'
Vue.use(Toast); // 注冊 Toast
```
最后我們在需要調用它的地方直接傳入配置項使用即可,比如:
```
this.$toast('Hello World', 2000);
```
當然你也可以不使用 install 方法來編寫插件,直接采用導出一個封裝好的實例方法并將其掛載到 Vue 的原型鏈上來實現相同的功能。
更詳細的編寫插件和實例的方法可以參考我之前寫的一篇文章:[Vue 插件編寫與實戰](https://mp.weixin.qq.com/s/Aqgh7Dkialhm9v8U0wBuqg)
## 結語
本文講解了編寫可復用性模塊的常見方法,通過出現了重復代碼 -> 封裝成一個變量 -> 封裝成一個函數 -> 封裝成一個組件 -> 封裝成一個插件,一步步將重復代碼進行分析和復用。而與重復代碼做斗爭是一個持久性的過程,我們需要時刻保持一種“強迫癥”的心態去整理復用項目中的重復代碼,做好編碼的嚴謹和自律。
## 思考 & 作業
* 在 Vue 中如何添加全局自定義指令?
* 在 vue 路由切換時如何全局隱藏某個插件?比如文中的 toast
* 如何實現一個表單驗證插件?需要運用到哪些知識?
- 開篇: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:嘗試使用外部數據
- 總結篇:寫在最后