【轉存】Vue元件選項props
原帖地址
前面的話
元件接受的選項大部分與Vue例項一樣,而選項props是元件中非常重要的一個選項。在 Vue 中,父子元件的關係可以總結為 props down, events up。父元件通過 props 向下傳遞資料給子元件,子元件通過 events 給父元件傳送訊息。本文將詳細介紹Vue元件選項props
父子級元件
在介紹props之前,先介紹父子級元件的寫法
在一個良好定義的介面中儘可能將父子元件解耦是很重要的。這保證了每個元件可以在相對隔離的環境中書寫和理解,也大幅提高了元件的可維護性和可重用性
【錯誤寫法】
現在來介紹兩種父子級元件的錯誤寫法
下面這種形式的寫法是錯誤的,因為當子元件註冊到父元件時,Vue.js會編譯好父元件的模板,模板的內容已經決定了父元件將要渲染的HTML <parent>...</parent>
執行時,它的一些子標籤只會被當作普通的HTML來執行,<child></child>不是標準的HTML標籤,會被瀏覽器直接忽視掉
<div id="example"> <parent> <child></child> <child></child> </parent> </div>
在父元件標籤之外使用子元件也是錯誤的
<div id="example"> <parent></parent> <child></child> </div>
【正確寫法】
<div id="example"> <parent></parent> </div>
<script> var childNode = { template: '<div>childNode</div>', } var parentNode = { template: ` <div class="parent"> <child></child> <child></child> </div> `, components: { 'child': childNode } }; // 建立根例項 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
靜態props
元件例項的作用域是孤立的。這意味著不能 (也不應該) 在子元件的模板內直接引用父元件的資料。要讓子元件使用父元件的資料,需要通過子元件的 props 選項
使用Prop傳遞資料包括靜態和動態兩種形式,下面先介紹靜態props
子元件要顯式地用 props
選項宣告它期待獲得的資料
var childNode = { template: '<div>{{message}}</div>', props:['message'] }
靜態Prop通過為子元件在父元件中的佔位符新增特性的方式來達到傳值的目的
<div id="example"> <parent></parent> </div>
<script> 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 } }; // 建立根例項 new Vue({ el: '#example', components: { 'parent': parentNode } }) </script>
命名約定
對於props宣告的屬性來說,在父級HTML模板中,屬性名需要使用中劃線寫法
var parentNode = { template: ` <div class="parent"> <child my-message="aaa"></child> <child my-message="bbb"></child> </div>`, components: { 'child': childNode } };
子級props屬性宣告時,使用小駝峰或者中劃線寫法都可以;而子級模板使用從父級傳來的變數時,需要使用對應的小駝峰寫法
var childNode = { template: '<div>{{myMessage}}</div>', props:['myMessage'] }
var childNode = { template: '<div>{{myMessage}}</div>', props:['my-message'] }
動態props
在模板中,要動態地繫結父元件的資料到子模板的 props,與繫結到任何普通的HTML特性相類似,就是用 v-bind
。每當父元件的資料變化時,該變化也會傳導給子元件
var childNode = { template: '<div>{{myMessage}}</div>', props:['myMessage'] }
var parentNode = { template: ` <div class="parent"> <child :my-message="data1"></child> <child :my-message="data2"></child> </div>`, components: { 'child': childNode }, data(){ return { 'data1':'aaa', 'data2':'bbb' } } };
傳遞數字
初學者常犯的一個錯誤是使用字面量語法傳遞數值
<!-- 傳遞了一個字串 "1" --> <comp some-prop="1"></comp>
<div id="example"> <my-parent></my-parent> </div>
<script> var childNode = { template: '<div>{{myMessage}}的型別是{{type}}</div>', props:['myMessage'], computed:{ type(){ return typeof this.myMessage } } } var parentNode = { template: ` <div class="parent"> <my-child my-message="1"></my-child> </div>`, components: { 'myChild': childNode } }; // 建立根例項 new Vue({ el: '#example', components: { 'MyParent': parentNode } }) </script>
因為它是一個字面 prop,它的值是字串 "1"
而不是 number。如果想傳遞一個實際的 number,需要使用 v-bind
,從而讓它的值被當作JS表示式計算
<!-- 傳遞實際的 number --> <comp v-bind:some-prop="1"></comp>
var parentNode = { template: ` <div class="parent"> <my-child :my-message="1"></my-child> </div>`, components: { 'myChild': childNode } };
或者可以使用動態props,在data屬性中設定對應的數字1
var parentNode = { template: ` <div class="parent"> <my-child :my-message="data"></my-child> </div>`, components: { 'myChild': childNode }, data(){ return { 'data': 1 } } };
props驗證
可以為元件的 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 } } };
單向資料流
prop 是單向繫結的:當父元件的屬性變化時,將傳導給子元件,但是不會反過來。這是為了防止子元件無意修改了父元件的狀態——這會讓應用的資料流難以理解
另外,每次父元件更新時,子元件的所有 prop 都會更新為最新值。這意味著不應該在子元件內部改變 prop。如果這麼做了,Vue 會在控制檯給出警告
下面是一個典型例子
<div id="example"> <parent></parent> </div>
<script> var childNode = { template: ` <div class="child"> <div> <span>子元件資料</span> <input v-model="childMsg"> </div> <p>{{childMsg}}</p> </div> `, props:['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資料
修改prop中的資料,通常有以下兩種原因
1、prop 作為初始值傳入後,子元件想把它當作區域性資料來用
2、prop 作為初始值傳入,由子元件處理成其它資料輸出
[注意]JS中物件和陣列是引用型別,指向同一個記憶體空間,如果 prop 是一個物件或陣列,在子元件內部改變它會影響父元件的狀態
對於這兩種情況,正確的應對方式是
1、定義一個區域性變數,並用 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>
下面示例中,除初始值外,父元件的值無法更新到子元件中
2、定義一個計算屬性,處理 prop 的值並返回
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
但是,由於是計算屬性,則只能顯示值,而不能設定值
<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>
下面示例中,由於子元件使用的是計算屬性,所以,子元件的資料無法手動修改
3、更加妥帖的方案是,使用變數儲存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>