組件是可復用的Vue實例,擁有屬于自己的數據、模板、腳本和樣式,可避免繁重的重復性開發。由于組件都是獨立的,因此其內部代碼不會影響其它組件,但可以包含其它組件,并且相互之間還能通信。
## 一、注冊
  在使用組件之前,需要先將其注冊,Vue提供了兩種注冊方式:全局注冊和局部注冊。
**1)全局注冊**
  通過Vue.component()方法可注冊全局的組件,它能接收兩個參數,第一個是組件名稱,第二個既可以是擴展過的構造器(即Vue.extend()的返回值),也可以是選項對象(會自動調用Vue.extend()),如下所示。
~~~js
Vue.component("btn-custom", Vue.extend({ })); //擴展過的構造器
Vue.component("btn-custom", { }); //選項對象
~~~
  組件的選項對象也會包含data、methods、計算屬性和生命周期鉤子等成員,但不包含掛載目標el選項,并且data選項也不再是一個對象而是一個函數,因為只有這樣才能讓每個實例維護各自的數據對象,互不影響。注意,只有在組件注冊之后才能將其應用于其它Vue根實例的模板中,如下所示。
~~~html
<div id="container">
<btn-custom></btn-custom>
</div>
<script>
Vue.component("btn-custom", {
data: function() {
return {
txt: "提交"
};
},
template: '<button>{{txt}}</button>'
});
var vm = new Vue({
el: "#container"
});
</script>
~~~
  渲染出的DOM結構如下所示。
~~~html
<div id="container">
<button>提交</button>
</div>
~~~
  組件的命名方式除了上面的連字符分隔式之外,還有另一種大駝峰式(例如BtnCustom)。當把組件引用至字符串模板中時,兩種命名方式都是有效的;而當把組件直接應用到DOM模板中時(如下所示),就不能用大駝峰命名,因為標簽會被自動轉換成小寫(即\<btncustom>),于是就找不到這個組件的定義,進而拋出錯誤。
~~~html
<div id="container">
<BtnCustom></BtnCustom>
</div>
~~~
**2)局部注冊**
  Vue的局部注冊需要分兩步,首先通過創建選項對象的方式來定義組件,如下所示。
~~~js
var BtnCustom = {
data: function() {
return {
txt: "提交"
};
},
template: "<button>{{txt}}</button>"
};
~~~
  然后在Vue根實例的components選項中注冊要使用的組件(如下所示),其中屬性名就是模板中要使用的自定義元素名,屬性值就是組件。
~~~js
var vm = new Vue({
el: "#container",
components: {
"btn-custom": BtnCustom
}
});
~~~
  注意,當構建一個組件時,其模板中必須包含一個根元素,之前的示例都只有一個元素。如果有多個元素,那么就得像下面這樣用一個元素(\<div>)包裹其它元素(\<span>和\<button>)。
~~~js
var BtnCustom = {
template: `<div>
<span>按鈕</span>
<button>提交</button>
</div>`
};
~~~
## 二、數據傳遞
  組件的props選項能接收從外部(可以是父組件)傳遞進來的數據,其值是一個由HTML特性組成的數組或對象,如下所示。
~~~html
<btn-custom in-html="提交"></btn-custom>
<script>
Vue.component("btn-custom", {
props: ["inHtml"],
template: '<button>{{inHtml}}</button>'
});
</script>
~~~
  由于HTML特性的名稱大小寫不敏感,因此瀏覽器會將所有大寫字母自動轉換成小寫。這意味著如果在組件內為props選項添加駝峰式的特性(例如inHtml),那么在DOM模板中需要聲明成等價的連字符分隔式的特性(例如in-html),否則在組件內將讀取不到該特性。有一點要注意,在字符串模板中使用特性,兩種命名方式都是有效的。
**1)動態傳值**
  特性值的類型除了上文的字符串之外,還可以通過v-bind指令動態的將任意類型傳遞給組件的props選項,例如傳入一個數字,如下所示。
~~~html
<btn-custom :digit="1"></btn-custom>
<script>
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
typeof this.digit; //"number"
}
});
</script>
~~~
  在created鉤子中調用typeof運算符計算this.digit,得到的值為“number”,說明數字傳遞成功。
  如果要傳遞對象的所有屬性,那么不必一個一個聲明,只需要不定義v-bind的參數即可,如下所示,兩個btn-custom組件是等價的。
~~~html
<div id="container">
<btn-custom v-bind="obj"></btn-custom>
<!-- 相當于 -->
<btn-custom :id="obj.id" :name="obj.name"></btn-custom>
</div>
<script>
Vue.component("btn-custom", {
props: ["id"],
template: '<button>{{id}}</button>'
});
var vm = new Vue({
el: "#container",
data: {
obj: { id: 1, name: "strick" }
}
});
</script>
~~~
  注意,在props選項中聲明的是id或name,而不是obj。
**2)數據流**
  在Vue中,組件之間的數據是自頂向下單向流動的(即單向數據流),父組件通過props將數據傳遞給子組件。一旦父組件的數據有所更新,那么子組件也會自動更新,如果在子組件中修改接收的props(例如下面的digit特性),那么Vue會拋出錯誤警告,避免改變父組件的狀態。
~~~js
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
this.digit = 2;
}
});
~~~
  很多需要改變props的情況,其實都能以另一種更合理的方式解決,例如將其保存到組件的data屬性中或定義成一個計算屬性等。
**3)校驗特性**
  組件的props能以對象的形式指定值類型,其鍵是接收的特性名稱,值是類型構造函數。這樣既有助于閱讀,也可以避免傳遞無效的值。在下面的示例中,指定了digit必須是數字,而number既可以是數字也可以是字符串。
~~~js
Vue.component("btn-custom", {
props: {
digit: Number,
number: [Number, String]
}
});
~~~
  除了Number和String之外,內置的構造函數還有Boolean、Array、Object、Date、Function和Symbol。不僅如此,還可以自定義構造函數,通過instanceof運算符來檢查。在下面的示例中,驗證man特性是否是通過new Person()創建的。
~~~js
Vue.component("btn-custom", {
props: {
man: People
}
});
function People(name) {
this.name = name;
}
~~~
  除了基礎的類型檢查之外,組件還允許自定義驗證函數、添加必填標記和附帶默認值,如下所示。
~~~js
Vue.component("btn-custom", {
props: {
digit: {
type: Number,
required: true //必填
},
number: {
type: Number,
default: 100 //數字默認值
},
people: {
type: Object,
default: function() { //對象默認值
return { name: "strick" };
}
},
name: {
validator: function(value) { //驗證函數
return value.length > 5;
}
}
}
});
~~~
  在使用這些校驗規則時,有兩點需要注意:
  (1)當默認值是對象或數組時,需要從函數中獲取。
  (2)由于props會在組件實例創建之前進行驗證,因此在default()和validator()函數中不能使用組件的屬性,例如data、computed、methods等。
**4)未在props中的特性**
  組件可以聲明任意多個特性,而那些沒有在props中定義的特性不但會被保存到實例屬性$attrs中,還會被添加到根元素上。注意,class和style兩個特性未包含在$attrs屬性中,并且它們會與原特性進行合并,而不是替換。以下面的btn-custom組件為例,根元素\<button>會接收type和class兩個特性。
~~~html
<btn-custom type="submit" class="size"></btn-custom>
<script>
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
console.log(this.$attrs); //{type: "submit"}
},
template: '<button type="button" class="warning">{{digit}}</button>'
});
</script>
~~~
  渲染出的\<button>元素如下所示,其中type的值被替換成了“submit”,而class的值變成了“warning size”。
~~~html
<button type="submit" class="warning size"></button>
~~~
  如果不想讓根元素繼承特性,那么可以將組件的inheritAttrs選項設為false,但要注意,inheritAttrs不會影響class和style的傳遞。還是以btn-custom組件為例,props和template兩個選項與之前相同。
~~~html
<btn-custom type="submit" class="size"></btn-custom>
<script>
Vue.component("btn-custom", {
inheritAttrs: false
});
</script>
~~~
  渲染出的\<button>元素如下所示,其中type的值未被替換,而class的值仍然是“warning size”。
~~~html
<button type="button" class="warning size"></button>
~~~
## 三、混入
  混入(mixin)是一種代碼復用技術,一個混入對象可包含任意組件選項,并能將其與普通組件混合在一起。
**1)選項合并策略**
  當組件和混入對象包含同名選項時,這些選項將會通過2種策略進行合并。
  (1)當數據對象或值為對象的選項(例如methods、components等)發生沖突時,同名的屬性將以組件的為準。如下代碼所示,雖然混入對象Mixin的數據對象也包含name屬性,但是依然會被btn-custom組件中的name屬性所覆蓋,并且它的getName()也會被替換。
~~~js
var Mixin = {
data: function() {
return { name: "strick" };
},
methods: {
getName: function() {
console.log("mixin");
}
}
};
Vue.component("btn-custom", {
mixins: [Mixin],
data: function() {
return { name: "freedom" };
},
methods: {
getName: function() {
console.log("component");
}
}
});
~~~
  (2)當生命周期鉤子發生沖突時,同名的鉤子將合并成一個數組,混入對象的鉤子在前,組件的鉤子在后,如下所示,先輸出“mixin”,再輸出“component”。
~~~js
var Mixin = {
created: function() {
console.log("mixin");
}
};
Vue.component("btn-custom", {
mixins: [Mixin],
created: function() {
console.log("component");
}
});
~~~
**2)全局混入**
  通過Vue.mixin()方法可注冊全局的混入對象,如下所示。
~~~js
Vue.mixin({
created: function () {
console.log("global");
}
});
~~~
  全局混入會影響所有的Vue實例,包括自定義的組件或第三方組件,因此要謹慎使用。大部分情況下它只適合自定義的選項,在官方的代碼風格指南中,為混入中的這些選項制訂了專門的命名規范,即以“$\_”和自定義的命名空間為前綴(例如$\_namespace\_),從而避免與其它實例中的選項相沖突,下面是一個簡單的示例。
~~~js
Vue.mixin({
$_namespace_getAge: function () {
return 28;
}
});
~~~
**3)自定義選項合并策略**
  除了預定義的合并策略之外,Vue還允許自定義合并策略,只需在Vue.config.optionMergeStrategies中添加一個包含合并邏輯的函數即可。
  下面是一個示例,首先在混入對象和組件中都聲明了一個自定義的age選項;然后在Vue.config.optionMergeStrategies中添加一個同名的age()函數,并且需要在組件之前聲明合并函數;最后在created鉤子中調用實例屬性$options,讀取到的age值為28,符合age()函數中的合并規則。
~~~js
var Mixin = {
age: 28
};
Vue.config.optionMergeStrategies.age = function(toVal, fromVal) {
return fromVal > toVal ? toVal : fromVal;
};
Vue.component("btn-custom", {
mixins: [Mixin],
created: function() {
this.$options.age; //28
},
age: 30
});
~~~
## **四、動態組件**
  Vue內置的\<component>元素可渲染一個元組件為動態組件,通過它的is特性來決定使用哪個組件。下面用一個例子來演示\<component>元素的用法,首先全局注冊兩個組件tab1和tab2;然后將它們合并成數組賦給vm實例的tabs屬性,而另一個current屬性記錄了當前要渲染的組件,默認值為tab1;最后將該屬性值傳遞給is特性,并在DOM模板中創建兩個按鈕,每個按鈕都注冊了點擊事件,可更改要渲染的組件。
~~~html
<div id="container">
<button v-for="tab in tabs" @click="current = tab">{{ tab }}</button>
<component :is="current"></component>
</div>
<script>
Vue.component("tab1", {
template: '<input type="text"/>'
});
Vue.component("tab2", {
template: '<input type="text"/>'
});
var vm = new Vue({
el: "#container",
data: {
current: "tab1",
tabs: ["tab1", "tab2"]
}
});
</script>
~~~
**1)\<keep-alive>**
  雖然可以動態切換組件,但是組件的狀態無法保持,例如在tab1組件的文本框中輸入字符,來回切換后,這些字符就消失了。如果要緩存組件的狀態,那么可以用Vue提供的另一個內置的\<keep-alive>元素,如下所示,用它來包裹\<component>元素,就不會銷毀失活的組件,從而提升渲染性能。
~~~html
<keep-alive>
<component :is="current"></component>
</keep-alive>
~~~
  注意,\<keep-alive>元素自身不會渲染成一個DOM元素,并且其可與任意元素配合,但子元素只能渲染一個。由此可知,\<keep-alive>元素內可包含條件指令(如下所示),但不能包含v-for指令。
~~~html
<keep-alive>
<tab1 v-if="current == 'tab1'"></tab1>
<tab2 v-else></tab2>
<keep-alive>
~~~
  有兩個與\<keep-alive>元素相關的生命周期鉤子:activated和deactivated。以之前的tab1組件為例,為其添加這兩個鉤子(如下代碼所示),它被包裹在\<keep-alive>元素中。當激活tab1組件時,會觸發activated鉤子;而當停用tab1組件時,會觸發deactivated鉤子。
~~~js
Vue.component("tab1", {
template: '<input type="text"/>',
activated: function() {
console.log("activated");
},
deactivated: function() {
console.log("deactivated");
}
});
~~~
*****
> 原文出處:
[博客園-Vue躬行記](https://www.cnblogs.com/strick/category/1512864.html)
[知乎專欄-Vue躬行記](https://zhuanlan.zhihu.com/pwvue)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020