Vue.component('my-component-name', { / ... / })javascript
在組件的祖冊中,第一個參數爲組件的名稱。vue
命名方案:java
在引用其自定義元素時,兩種方案均可以使用。但直接在DOM中引用自定義元素時串聯式命名時惟一有效的方式。webpack
全局註冊的組件能夠在以後經過new Vue建立的Vue根實例的模板中引用。web
Vue.component('my-component-name', { // ... options ... })
先將組件定義爲純JavaScript對象。正則表達式
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
而後在建立Vue根實例的時候,components選項中定義所須要用到的組件。
對於components對象的每一個屬性,對象的key是自定義元素的名稱,而value包含着組件的選項對象。數組
new Vue({ el:'#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
局部註冊的組件在子組件中沒法訪問,若是想在ComponentB中訪問ComponentA,則應該瀏覽器
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, ...... }
若是使用ES2015模塊,則相似這樣app
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... }
建議建立一個component目錄,每一個組件都定義在這個文件中。
在局部註冊這些組件以前,須要預先導入每一個須要的組件。函數
import ComponentA from './ComponentA' import ComponentC from './ComponentC' export default { components: { ComponentA, ComponentC }, // ... }
這樣就能夠在componentB組件的模板內部引用ComponentA和ComponentB了。
基本組件:許多相對通用的組件(內部可能只含有一個 input 或 button 元素)而且每每在其餘組件中頻繁使用的一類組件。
這致使的結果是許多組件會列出很長的基礎組件清單,而後在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)中,經過全局方式導入基礎組件。
全局註冊方式必須在Vue根實例建立以前置入組件。
import Vue from 'vue' import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase' const requireComponent = require.context( // components 文件夾的相對路徑 './components', // 是否查找子文件夾 false, // 用於匹配組件文件名的正則表達式 /Base[A-Z]\w+\.(vue|js)$/ ) requireComponent.keys().forEach(fileName => { // 獲取組件配置 const componentConfig = requireComponent(fileName) // 取得組件的 Pascal 式命名 const componentName = upperFirst( camelCase( // 將文件名前面的 `'./` 和擴展名剝離 fileName.replace(/^\.\/(.*)\.\w+$/, '$1') ) ) // 以全局方式註冊組件 Vue.component( componentName, // 若是組件是經過 `export default` 導出, // 則在 `.default` 中,查找組件選項, // 不然回退至模塊根對象中,查找組件選項 componentConfig.default || componentConfig ) })
用於父組件對子組件傳遞信息。
HTML 屬性名稱對大小寫不敏感,所以瀏覽器會將全部大寫字符解釋爲小寫字符。
當在DOM 模板中書寫 prop 時,應當將駝峯式轉寫爲等價的串聯式。
若是是在使用字符串模板的場景,則沒有這些限制。
Vue.component('blog-post', { // 在 JavaScript 中使用駝峯式(camelCase) props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' }) <!-- 在 HTML 中使用串聯式(kebab-case) --> <blog-post post-title="hello!"></blog-post>
靜態Prop經過爲子組件在父組件中的佔位符添加特性的方式來達到傳值的目的
<div id="example"> <parent></parent> </div> <script type="text/javascript"> var childNode = { template:` <div>{{ message }}</div>`, props: ['message'] } var parentNode = { template:` <div class="parent"> <child message="aaa"></child> <child message="bbb"></child> </div>`, components: { 'child':childNode } } var app1 = new Vue({ el: '#example', components: { 'parent':parentNode } })
經過v-bind給props分配動態的值,每當父組件的數據變化時,該變化也會傳導給子組件:
<div id="app2"> <parent2></parent2> </div> // 動態prop var childNode2 ={ template:`<div>{{myMessage}}</div>`, props:['myMessage'] } var parentNode2 ={ template:` <div> <child :myMessage="data1"></child> <child :myMessage="data2"></child> </div>`, components:{ 'child':childNode2 }, data(){ return{ 'data1':'動態aaa', 'data2':'動態bbb' } } } var app2 = new Vue({ el:'#app2', components:{ 'parent2':parentNode2 } })
在這兩個例子中咱們向prop傳遞的都是字符串,實際上也能夠傳遞任意類型的值。
傳遞數字時若是使用「1」字面量則傳遞的是字符串而非數字。
須要使用 v-bind傳遞一個實際的 number,從而讓它的值被看成JS表達式計算.
<!--傳遞數值--> <div id="app3"> <my-parent></my-parent> </div> // 傳遞數值 var childNode3 = { template:`<div>{{myMessage}}的類型是{{type}}</div>`, props:['myMessage'], computed:{ type(){ return typeof this.myMessage } } } var parentNode3 = { template:` <div> <my-child :my-message="1"></my-child> </div>`, components:{ 'myChild':childNode3 } } var app3 = new Vue({ el:'#app3', components:{ 'MyParent':parentNode3 } })
或者可使用動態props,在data屬性中設置對應的數字1。
<!-- 沒有設定值得屬性默認值爲‘true’ --> <blog-post favorited></blog-post> <!-- `false` 是靜態的,這就須要咱們使用 v-bind, --> <!-- 來告訴 Vue 它是以 JavaScript 表達式表現,而不是一個字符串 --> <base-input v-bind:favorited="false"> <!-- 將一個變量,動態地分配到屬性值上 --> <base-input v-bind:favorited="post.currentUserFavorited">
<!-- array 是靜態的,這就須要咱們使用 v-bind, --> <!-- 來告訴 Vue 它是以 JavaScript 表達式表現,而不是一個字符串 --> <blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post> <!-- 將一個變量,動態地分配到屬性值上 --> <blog-post v-bind:comment-ids="post.commentIds"></blog-post>
<!-- object 是靜態的,這就須要咱們使用 v-bind, --> <!-- 來告訴 Vue 它是以 JavaScript 表達式表現,而不是一個字符串 --> <blog-post v-bind:comments="{ id: 1, title: '個人 Vue 旅程' }"></blog-post> <!-- 將一個變量,動態地分配到屬性值上 --> <blog-post v-bind:post="post"></blog-post>
post: { id: 1, title: '個人 Vue 旅程' } // 如下模板: <blog-post v-bind="post"></blog-post> // 等價於: <blog-post v-bind:id="post.id" v-bind:title="post.title" ></blog-post>
prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,可是不會反過來。這是爲了防止子組件無心修改了父組件的狀態——這會讓應用的數據流難以理解
另外,每次父組件更新時,子組件的全部 prop 都會更新爲最新值。這意味着不該該在子組件內部改變 prop。若是這麼作了,Vue 會在控制檯給出警告
下面是一個典型例子
<!--單向數據流--> <div id="app4"> <parent4></parent4> </div> // 單向數據流 var childNode4 = { template:` <div class="child"> <div> <span>子組件數據</span> <input v-model="childMsg"> </div> <p>{{childMsg}}</p> </div>`, props:['childMsg'] } var parentNode4 = { template:` <div class="parent"> <div> <span>父組件數據</span> <input v-model="Msg"> </div> <p>{{Msg}}</p> <child :child-msg="Msg"></child> </div>`, components:{ 'child':childNode4 }, data(){ return{ 'Msg':'August' } } } var app4 = new Vue({ el:'#app4', components:{ 'parent4':parentNode4 } })
父組件數據變化時,子組件數據會相應變化;而子組件數據變化時,父組件數據不變,並在控制檯顯示警告。
修改prop中的數據,一般有如下兩種緣由
一、prop 做爲初始值傳入後,子組件想把它看成局部數據來用
二、prop 做爲初始值傳入,由子組件處理成其它數據輸出
[注意]JS中對象和數組是引用類型,指向同一個內存空間,若是 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態
對於這兩種狀況,正確的應對方式是
一、定義一個局部變量,並用 prop 的值初始化它
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
可是,定義的局部變量counter只能接受initialCounter的初始值,當父組件要傳遞的值發生變化時,counter沒法接收到最新值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子組件數據</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, }; var parentNode = { template: ` <div class="parent"> <div> <span>父組件數據</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 建立根實例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
除初始值外,父組件的值沒法更新到子組件中。
二、定義一個計算屬性,處理 prop 的值並返回
<script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子組件數據</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], computed:{ temp(){ return this.childMsg } }, }; var parentNode = { template: ` <div class="parent"> <div> <span>父組件數據</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 建立根實例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
因爲子組件使用的是計算屬性,因此,子組件的數據沒法手動修改。
三、更加妥帖的方案是,使用變量儲存prop的初始值,並使用watch來觀察prop的值的變化。發生變化時,更新變量的值
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div> <span>子組件數據</span> <input v-model="temp"> </div> <p>{{temp}}</p> </div> `, props:['childMsg'], data(){ return{ temp:this.childMsg } }, watch:{ childMsg(){ this.temp = this.childMsg } } }; var parentNode = { template: ` <div class="parent"> <div> <span>父組件數據</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child :child-msg="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':'match' } } }; // 建立根實例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
能夠爲組件的 props 指定驗證規格。若是傳入的數據不符合規格,Vue會發出警告。當組件給其餘人使用時,這頗有用
要指定驗證規格,須要用對象的形式,而不能用字符串數組
Vue.component('example', { props: { // 基礎類型檢測 (`null` 意思是任何類型均可以) propA: Number, // 多種類型 propB: [String, Number], // 必傳且是字符串 propC: { type: String, required: true }, // 數字,有默認值 propD: { type: Number, default: 100 }, // 數組/對象的默認值應當由一個工廠函數返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定義驗證函數 propF: { validator: function (value) { return value > 10 } } } })
type 能夠是下面原生構造器
String Number Boolean Function Object Array Symbol
type 也能夠是一個自定義構造器函數,使用 instanceof 檢測。
當 prop 驗證失敗,Vue 會在拋出警告 (若是使用的是開發版本)。props會在組件實例建立以前進行校驗,因此在 default 或 validator 函數裏,諸如 data、computed 或 methods 等實例屬性還沒法使用
下面是一個簡單例子,若是傳入子組件的message不是數字,則拋出警告
<div id="example"> <parent></parent> </div> <script> var childNode = { template: '<div>{{message}}</div>', props:{ 'message':Number } } var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: { 'child': childNode }, data(){ return{ msg: '123' } } }; // 建立根實例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
傳入數字123時,則無警告提示。傳入字符串'123'時,就有警告。
將上面代碼中,子組件的內容修改以下,可自定義驗證函數,當函數返回爲false時,則輸出警告提示
var childNode = { template: '<div>{{message}}</div>', props:{ 'message':{ validator: function (value) { return value > 10 } } } }
在父組件中傳入msg值爲1,因爲小於10,則輸出警告提示
var parentNode = { template: ` <div class="parent"> <child :message="msg"></child> </div>`, components: { 'child': childNode }, data(){ return{ msg:1 } } };
父組件使用props傳遞數據給子組件,子組件怎麼跟父組件通訊呢?這時,Vue的自定義事件就派上用場了。
每一個 Vue 實例都實現了事件接口 (Events interface),即
使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件
父組件能夠在使用子組件的地方直接用 v-on 來監聽子組件觸發的事件。
<!--自定義事件--> <div id="app5"> <parent5></parent5> </div>
// 自定義事件 var childNode5 = { template:` <button @click="incrementCounter">{{counter}}</button>`, data(){ return{ counter:0 } }, methods:{ incrementCounter(){ this.counter++; this.$emit('increment'); } } } var parentNode5 = { template:` <div class="parent"> <p>{{total}}</p> <child @increment="incrementTotal"></child> <child @increment="incrementTotal"></child> </div>`, data(){ return{ 'total':0 } }, methods:{ incrementTotal(){ this.total++; } }, components:{ 'child':childNode5 } } var app5 = new Vue({ el:'#app5', components:{ 'parent5':parentNode5 } })
自定義事件的命名約定與組件註冊及props的命名約定都不相同,因爲自定義事件實質上也是屬於HTML的屬性,因此其在HTML模板中,最好使用中劃線形式
<child @pass-data="getData"></child>
而子組件中觸發事件時,一樣使用中劃線形式
this.$emit('pass-data',this.childMsg)
子組件經過$emit能夠觸發事件,第一個參數爲要觸發的事件,第二個事件爲要傳遞的數據
父組件經過$on監聽事件,事件處理函數的參數則爲接收的數據
<!--數據傳遞--> <div id="app6"> <parent6></parent6> </div> // 數據傳遞 var childNode6 = { template:` <div class="child"> <div> <span>子組件數據</span> <input v-model="childMsg" @input="data"> </div> <p>{{childMsg}}</p> </div>`, data(){ return{ childMsg:'' } }, methods:{ data(){ this.$emit('pass-data',this.childMsg) } } } var parentNode6 = { template:` <div class="parent"> <div> <span>父組件數據</span> <input v-model="msg"> </div> <p>{{msg}}</p> <child @pass-data="getData"></child> </div>`, components:{ 'child':childNode6 }, data(){ return{ 'msg':'August' } }, methods:{ getData(value){ this.msg = value; } } } var app6 = new Vue({ el:'#app6', components:{ 'parent6':parentNode6 } })
修改子組件中的input值,則父組件到接收到相同值,則顯示出來
在一些狀況下,可能會須要對一個 prop 進行雙向綁定。事實上,這正是Vue1.x中的 .sync修飾符所提供的功能。當一個子組件改變了一個 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會致使問題,由於它破壞了單向數據流的假設。 因爲子組件改變 prop 的代碼和普通的狀態改動代碼毫無區別,當光看子組件的代碼時,徹底不知道它什麼時候悄悄地改變了父組件的狀態。這在 debug 複雜結構的應用時會帶來很高的維護成本,上面所說的正是在 2.0 中移除 .sync 的理由
從 2.3.0 起從新引入了 .sync 修飾符,可是此次它只是做爲一個編譯時的語法糖存在。它會被擴展爲一個自動更新父組件屬性的 v-on 偵聽器。
<comp :foo.sync="bar"></comp>
會被擴展爲
<comp :foo="bar" @update:foo="val => bar = val"></comp>
當子組件須要更新 foo 的值時,它須要顯式地觸發一個更新事件:
this.$emit('update:foo', newValue)
所以,可使用.sync來簡化自定義事件的操做,實現子組件向父組件的數據傳遞
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <div>子組件數據:{{childMsg}}</div> <input v-model="childMsg"> <button @click=add >+1</button> </div> `, data(){ return{ childMsg: 0 } }, methods:{ add(){ this.childMsg++; this.$emit('update:foo',this.childMsg); } } }; var parentNode = { template: ` <div class="parent"> <p>父組件數據:{{msg}}</p> <child :foo.sync="msg"></child> </div> `, components: { 'child': childNode }, data(){ return { 'msg':0 } } }; // 建立根實例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
爲了讓組件能夠組合,須要一種方式來混合父組件的內容與子組件本身的模板。這個過程被稱爲 內容分發。
Vue實現了一個內容分發 API,參照了當前 Web 組件規範草案,使用特殊的 <slot> 元素做爲原始內容的插槽。
在深刻內容分發API以前,先明確內容在哪一個做用域裏編譯。假定模板爲
<child-component> {{ message }} </child-component>
message應該是綁定到父組件的數據仍是子組件的數據?答案是父組件。
組件做用域簡單地來講就是:父組件模板的內容在父組件做用域內編譯,子組件模板的內容在子組件做用域內編譯。
一個常見的錯誤是試圖在父組件模板內將一個指令綁定到子組件的屬性/方法:
<!-- 無效 --> <child-component v-show="someChildProperty"></child-component>
若是要綁定做用域內的指令到一個組件的根節點,應當在組件本身的模板上作:
Vue.component('child-component', { // 有效,由於是在正確的做用域內 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
相似的,分發內容是在父做用域內編譯。
通常地,若是子組件模板不包含<slot>插口,父組件的內容將會被丟棄
<div id="example"> <parent></parent> </div> <script src="https://unpkg.com/vue"></script> <script> var childNode = { template: ` <div class="child"> <p>子組件</p> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p>測試內容</p> </child> </div> `, components: { 'child': childNode }, }; // 建立根實例 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
以下圖所示,<child>所包含的<p>測試內容</p>被丟棄
若是子組件有 inline-template 特性,組件將把它的內容看成它的模板,而忽略真實的模板內容
可是 inline-template 讓模板的做用域難以理解
var childNode = { template: ` <div class="child"> <p>子組件</p> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child inline-template> <p>測試內容</p> </child> </div> `, components: { 'child': childNode }, };
當子組件模板只有一個沒有屬性的 slot 時,父組件整個內容片斷將插入到 slot 所在的 DOM 位置,並替換掉 slot 標籤自己
var childNode = { template: ` <div class="child"> <p>子組件</p> <slot></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p>測試內容</p> </child> </div> `, components: { 'child': childNode }, };
若是出現多於1個的匿名slot,vue將報錯
默認值
最初在 <slot> 標籤中的任何內容都被視爲備用內容,或者稱爲默認值。備用內容在子組件的做用域內編譯,而且只有在宿主元素爲空,且沒有要插入的內容時才顯示備用內容
當slot存在默認值,且父元素在<child>中沒有要插入的內容時,顯示默認值。
var childNode = { template: ` <div class="child"> <p>子組件</p> <slot><p>我是默認值</p></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child></child> </div> `, components: { 'child': childNode }, };
當slot存在默認值,且父元素在<child>中存在要插入的內容時,則顯示設置值
var childNode = { template: ` <div class="child"> <p>子組件</p> <slot><p>我是默認值</p></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p>我是設置值</p> </child> </div> `, components: { 'child': childNode }, };
<slot> 元素能夠用一個特殊的屬性 name 來配置如何分發內容。
多個 slot 能夠有不一樣的名字。
具名 slot 將匹配內容片斷中有對應 slot 特性的元素
var childNode = { template: ` <div class="child"> <p>子組件</p> <slot name="my-header">頭部默認值</slot> <slot name="my-body">主體默認值</slot> <slot name="my-footer">尾部默認值</slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p slot="my-header">我是頭部</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
仍然能夠有一個匿名 slot,它是默認 slot,做爲找不到匹配的內容片斷的備用插槽。
匿名slot只能做爲沒有slot屬性的元素的插槽,有slot屬性的元素若是沒有配置slot,則會被拋棄。
var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p slot="my-body">我是主體</p> <p>我是其餘內容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p slot="my-body">我是主體</p> <p>我是其餘內容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
<p slot="my-body">插入<slot name="my-body">中,<p>我是其餘內容</p>插入<slot>中,而<p slot="my-footer">被丟棄
var childNode = { template: ` <div class="child"> <p>子組件</p> <slot name="my-body">主體默認值</slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <p slot="my-body">我是主體</p> <p>我是其餘內容</p> <p slot="my-footer">我是尾部</p> </child> </div> `, components: { 'child': childNode }, };
<p>我是其餘內容</p>和<p slot="my-footer">都被拋棄
做用域插槽是一種特殊類型的插槽,用做使用一個 (可以傳遞數據到) 可重用模板替換已渲染元素。
在子組件中,只需將數據傳遞到插槽,就像將 props 傳遞給組件同樣
<div class="child"> <slot text="hello from child"></slot> </div>
在父級中,具備特殊屬性 scope 的 <template> 元素必須存在,表示它是做用域插槽的模板。scope 的值對應一個臨時變量名,此變量接收從子組件中傳遞的 props 對象
var childNode = { template: ` <div class="child"> <p>子組件</p> <slot xxx="hello from child"></slot> </div> `, }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <template scope="props"> <p>hello from parent</p> <p>{{ props.xxx }}</p> </template> </child> </div> `, components: { 'child': childNode }, };
若是渲染以上結果,獲得的輸出是
列表組件
做用域插槽更具表明性的用例是列表組件,容許組件自定義應該如何渲染列表每一項
var childNode = { template: ` <ul> <slot name="item" v-for="item in items" :text="item.text">默認值</slot> </ul> `, data(){ return{ items:[ {id:1,text:'第1段'}, {id:2,text:'第2段'}, {id:3,text:'第3段'}, ] } } }; var parentNode = { template: ` <div class="parent"> <p>父組件</p> <child> <template slot="item" scope="props"> <li>{{ props.text }}</li> </template> </child> </div> `, components: { 'child': childNode }, };