組件從註冊方式分爲全局組件和局部組件。css
從功能類型又能夠分爲偏視圖表現的(presentational)和偏邏輯的(動態生成dom),推薦在前者中使用模板,在後者中使用 JSX 或渲染函數動態生成組件模板內容,總體來講表現類的組件遠遠多於邏輯類組件。html
注意:組件名最好使用全小寫加短橫線,即便用 kebab-case (短橫線分隔命名) 定義一個組件,在引用這個自定義元素時必須使用 kebab-case,vue
當使用 PascalCase (首字母大寫命名) 定義一個組件時,在引用這個自定義元素時兩種命名法均可以使用。webpack
Vue.component('MyComponentName', { /* ... */ })
也就是說 <my-component-name>
和 <MyComponentName>
都是可接受的。git
Vue.component,在new根實例以前定義全局組件github
// 定義一個名爲 button-counter 的全局組件 Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' }) //使用: <button-counter></button-counter>
顯示如圖:web
全局註冊的組件,對於再也不使用的,仍會被包含在最終的構建結果中,形成了用戶下載的 JavaScript 的增長。正則表達式
局部組件有幾種註冊形式,這裏介紹單文件組件。vue-router
一個單文件組件即一個vue文件,包含template(必須有),script,style三部分。vue-cli
JavaScript、CSS 分離成獨立的文件而後作到熱重載和預編譯。
<!-- my-component.vue --> <template> <div>This will be pre-compiled</div> </template> <script src="./my-component.js"></script> <style src="./my-component.css"></style>
每一個組件必須有且只有一個根元素,使用組件時,組件上的特性也會被添加到根元素上,根元素上已有的同名特性會被覆蓋,除了class和style,會與組件的class和style合併,詳細見下面「引用第三方庫」
//com.vue
<template lang="html"> <div>//只能有一個html標籤包裹裏面全部節點 child component{{name}}
</div> </template> <script> export default{
data(){//data部分必須是個函數,返回一個對象。當前組件可使用data裏面的數據
return {
name:'com'
}
}
}
</script>
<style lang="css">
</style>
引入單文件組件
//App.vue引入組件 import HelloWorld from "./components/HelloWorld"; //在頁面實例注入組件 components: { HelloWorld }, //在頁面使用組件 <HelloWorld /> //或者 <HelloWorld><HelloWorld />
組件的定義方法,參考邊界處理之模板定義的替代品。
關於template標籤。
每用一次組件,就會有一個它的新實例被建立,每一個組件都會各自獨立維護自身屬性,互相獨立不干擾,這也是爲何組件的data是一個函數的緣由:
<button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter>
上面例子中,App.vue的components上掛載了HelloWorld這個組件,其中App.vue是父組件,HelloWorld是子組件。
props(參考博文):父組件傳遞消息給子組件,爲單向的,意味着不應該在一個子組件內部改變 prop。
每次父組件發生更新時,子組件中全部的 prop 都將會刷新爲最新的。
//子組件中聲明props <template lang="html"> <div> 年齡:{{age}} </div> </template> <script> export default{ props:['age'], data(){ return{ name:'com' } } } </script> //組件特性傳入prop值 <com age="19"></com>//靜態數據 <com :age="ageNum"></com>//動態數據:變量
兩種常見的試圖改變一個 prop 的情形
一、這個 prop 用來傳遞一個初始值;這個子組件接下來但願將其做爲一個本地的 prop 數據來使用。在這種狀況下,最好定義一個本地的 data 屬性並將這個 prop 用做其初始值:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
二、這個 prop 以一種原始的值傳入且須要對這個值進行操做。在這種狀況下,最好使用這個 prop 的值來定義一個計算屬性:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中對象和數組是經過引用傳入的,因此對於一個數組或對象類型的 prop 來講,在子組件中改變這個對象或數組自己將會影響到父組件的狀態。
$emit:子組件向父組件傳遞數據
好比下面,點擊子組件的這個按鈕(子組件接收的用戶行爲),觸發組件自定義的patch事件,執行patch事件的處理函數msg3,改變父組件的ageNum數據
在子組件中代碼:
<button type="button" name="button" @click="$emit('patch',[參數])">發送到父組件</button>
父組件中代碼:
<com :age="ageNum" @patch='msg3'></com>//動態數據:變量
export default{
data(){
return {
ageNum: 18
}
},
methods: {
msg3(params){
//params:子組件傳遞過來的參數
this.ageNum++
}
}
}
vm.$attrs
屬性和vm.$listeners屬性(單向傳值,查閱博文:實例屬性和方法)
經常使用在跨級組件中,祖孫組件通訊。
查閱博文:v-model。
在一個組件的根元素上直接監聽一個原生事件,使用 v-on
的 .native
修飾符。
<base-input v-on:focus.native="onFocus"></base-input>
查閱博文:slot。
組件能夠接受任意的特性,而這些特性會被添加到這個組件的根元素上。
例如,你經過一個 Bootstrap 插件使用了一個第三方的 <bootstrap-date-input>
組件,而後這個 data-date-picker="activated"
特性就會自動添加到 <bootstrap-date-input>
這個組件的根元素上:
<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>
該組件根元素爲:
<input type="date" class="form-control">
替換/合併根元素已有特性
對於絕大多數特性來講,從外部提供給組件的值會替換掉組件內部設置好的值。因此若是組件上有 type="text"
,input的 type="date"
就會被替換掉並被破壞!class
和 style
特性會把兩邊的值會被合併起來,最終的值:form-control date-picker-theme-dark
。
最終,input爲:
<input
type="date"
data-date-picker="activated"
class="form-control date-picker-theme-dark">
禁用特性繼承
若是你不但願組件的根元素繼承特性,你能夠在組件的選項中設置 inheritAttrs: false
,該選項不會影響 style
和 class
的綁定。適合配合實例的 $attrs
屬性使用。
有些 HTML 元素,諸如 <ul>
、<ol>
、<table>
和 <select>
,對於哪些元素能夠出如今其內部是有嚴格限制的。而有些元素,諸如 <li>
、<tr>
和 <option>
,只能出如今其它某些特定的元素內部。
//這個自定義組件 <blog-post-row> 會被做爲無效的內容提高到外部 <table> <blog-post-row></blog-post-row> </table>
經過is特性解決:
<table> <tr is="blog-post-row"></tr> </table>
使用vue內置標籤<component>,經過添加is特性來切換不一樣的組件。
好比:在一個多標籤的界面中切換不一樣的組件(這個標籤的位置渲染爲那個組件)
<!-- 組件會在 `currentTabComponent` 改變時改變 --> <component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent
能夠是
當想保持組件被切換時的狀態,以免反覆重渲染致使的性能問題。
好比下例,posts的第二個文章被選中,切換到 Archive 標籤,而後再切換回 Posts,是不會繼續展現以前選擇的文章的。
需求:組件實例可以在它們第一次被建立的時候緩存下來。
方案:<keep-alive> 元素將動態組件包裹起來。
參考博文:vue內置標籤。
在須要的時候才從服務器加載一個模塊,Vue 提供一個工廠函數,resolve 回調會在從服務器獲得組件定義的時候被調用。
Vue.component('async-example', function (resolve, reject) { setTimeout(function () {//setTimeout 是爲了演示用的,如何獲取組件取決於業務。 // 向 `resolve` 回調傳遞組件定義 resolve({ template: '<div>I am async!</div>' }) }, 1000) })
一個推薦的作法是將異步組件和 webpack 的 code-splitting 功能一塊兒配合使用:
Vue.component('async-webpack-example', function (resolve) { // 這個特殊的 `require` 語法將會告訴 webpack // 自動將你的構建代碼切割成多個包,這些包 // 會經過 Ajax 請求加載 require(['./my-async-component'], resolve) })
也能夠在工廠函數中返回一個 Promise:
Vue.component( 'async-webpack-example', // 這個 `import` 函數會返回一個 `Promise` 對象。 () => import('./my-async-component') )
當使用局部註冊的時候,你也能夠直接提供一個返回 Promise
的函數:
new Vue({ // ... components: { 'my-component': () => import('./my-async-component') } })
處理加載狀態:
const AsyncComponent = () => ({ // 須要加載的組件 (應該是一個 `Promise` 對象) component: import('./MyComponent.vue'), // 異步組件加載時使用的組件 loading: LoadingComponent, // 加載失敗時使用的組件 error: ErrorComponent, // 展現加載時組件的延時時間。默認值是 200 (毫秒) delay: 200, // 若是提供了超時時間且組件加載也超時了, // 則使用加載失敗時使用的組件。默認值是:`Infinity` timeout: 3000 })
在 Vue Router 的路由組件中使用上述語法的話,必須使用 Vue Router 2.4.0+ 版本。
介紹組件使用中遇到的一些問題:父子孫組件的訪問,組件循環或者互相引用,模板其餘的定義方式,強制更新(數組或對象的變動檢測注意事項,或者可能依賴了非響應式狀態),在根元素使用v-once建立靜態組件。
推薦建立一個 components
目錄管理單文件組件。
針對基礎組件的長列表的改善:
import BaseButton from './BaseButton.vue' import BaseIcon from './BaseIcon.vue' import BaseInput from './BaseInput.vue' export default { components: { BaseButton, BaseIcon, BaseInput } }
使用webpack(或在內部使用了 webpack 的 Vue CLI 3+)的前提下,使用 require.context
全局註冊這些基礎組件。
如下代碼放在應用入口文件 (好比 src/main.js
) 中全局導入基礎組件
import Vue from 'vue' import upperFirst from 'lodash/upperFirst' 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, // 若是這個組件選項是經過 `export default` 導出的, // 那麼就會優先使用 `.default`, // 不然回退到使用模塊的根。 componentConfig.default || componentConfig ) })