DAY12 | 跟 Vue.js 認識的30天 - Vue 模組資料傳遞(`props`)
props
的命名及使用
HTML attribute 是大小寫不敏感的,所以必須要注意 prop
的命名跟使用。
命名
可以使用 PascalCase (首字母大寫) 或是 camelCase (駝峰命名法)的命名方法。
PascalCase :
PostTitle
、CartItem
、TodoItem
,每個單字的開頭都是大寫。camelCase :
postTitle
、cartItem
、todoItem
,除了第一個單字以外,其餘單字的開頭都是大小。
使用
雖然 prop
的命名是使用 PascalCase (首字母大寫) 或是 camelCase (駝峰命名法),但是在 HTML 中使用時必須使用 kebab-case (短橫線分隔)且應該為小寫。
像是 PostTitle
、 CartItem
、 TodoItem
等,在 HTML 中使用時就會變成 post-title
、 cart-item
、 todo-item
。
範例
1 | <div id="vm"> |
傳遞 props
值的方法
傳遞字串
1 | <blog-post post-title="Blog1" post-content="I\'m content1" post-complete="true" post-total-num="500" post="{title:'Blog1'}"></blog-post> |
只要是直接傳遞(靜態傳遞)的都是字串,所以 prop
接收的值 Blog1
、I\'m content1
、true
、500
、{...}
等等都是字串,而非是數字、布林值、陣列、物件等型別。
傳遞數字、布林值、陣列、物件
要如何傳遞數字、布林值、陣列、物件等值給 prop
接收,這時候就要借助 Vue 的指令 v-bind
。
1 | <blog-post post-title="動態傳遞" post-content="I\'m content1" v-bind:post-complete="true" v-bind:post-total-num="500" v-bind:post="{title:'動態傳遞'}"></blog-post> |
透過 v-bind
(可用縮寫 :
)來讓 Vue 知道後面的值的型別不是字串,而是數字、布林值、陣列或物件等。
也可以通過給予變數來獲得數字、布林值、陣列或物件等型別。
1 | <blog-post :post-title="postTitle" :post-content="postContent" :post-complete="postComplete" :post-total-num="postTotalNum" :post="post"></blog-post> |
單向數據流
props
是為了接收從父模組傳遞進來的資料,而這些資料是單向綁定的,也就是說父模組資料的更新會影響子模組裡的 prop
,但子模組裡的 prop
值的改變並不能影響父模組。
測試:
1 | <prop-change :counter=counter></prop-change> |
See the Pen [DAY12]跟 Vue.js 認識的30天 - Vue 模組資料傳遞(`props`) by Celeste6666 (@celeste6666) on CodePen.
從上面的測試可以得知:
外面(父模組)的資料
counter
改變會影響子模組prop
的counter
值的改變。子模組
prop
的counter
值的改變僅影響內部counter
值。不論子模組
prop
的counter
值是否有變動,只要父模組資料counter
改變時,子模組prop
的counter
值一定會連動。
改變子模組內 props
的值,使用以下幾種方法:
在
data
內創建一個值賦予
data
跟prop
初始值相同的值,且之後也是針對該data
內的值做操作,並且不會再受到該prop
的影響了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Vue.component("one-way-data", {
props: ["counter"],
template: `<div>
<span>component內的 {{newCounter}}</span>
<button type="button" @click="changeNewCounter">改變component數字</button>
</div>`,
data () {
return {
newCounter: this.counter
}
},
methods:{
changeNewCounter(){
this.newCounter+=10
}
}
});使用
computed
來做prop
值的轉換透過
computed
取得prop
值做複雜運算後的結果。1
2
3
4
5
6
7
8
9
10
11
12Vue.component("one-way-computed", {
props: ["counter"],
template: `<div>
<span>component內的 {{newCounter}}</span>
<button type="button">改變component數字</button>
</div>`,
computed: {
newCounter(){
return this.counter+100
}
}
});
物件型別的 prop 的傳遞
有關於物件或陣列的傳遞,在子模組中更改值是會影響父模組的喔!因為物件或陣列的傳遞是透過傳址(by reference)的方式,所以資料不管是在父模組還是子模組修改都會互相影響。
可以透過在 data
中創建 JSON.parse(JSON.stringify(物件))
的值,來使得內層跟外層的物件位址不同,而不會相互影響,但必須要注意到,使用 JSON.parse(JSON.stringify(物件))
所取得的 prop
值(物件),僅會取得初始的prop
值,之後不管 prop
值怎麼改變都不會再影響在 data
中新創建的值。
1 | { |
props
的進階使用
陣列型別
之前看到的 props
都是陣列型別,陣列型態傳遞進來的 props
值不會有任何限制,傳甚麼進來就接收甚麼。
1 | props: ['PostTitle', 'postContent', 'postAuthor'] |
物件型別及驗證
物件型別的 props
可以幫助我們指定每個 prop 的型別(type
)、默認值(default
)、是否必填(required
)或驗證是否成功(validator
)。
範例
1 | props: { |
prop
的物件屬性提醒:
type
: 可以是String
、Number
、Boolean
、Array
、Object
、Date
、Function
、Symbol
、自定義建構函式或上述類型組成的陣列(該prop
值可以擁有多種可能性)。1
2
3
4
5props:{
validationCounter:{
type: Number
}
}validationCounter
傳進來的值一定要是數字。default
: 如果該prop
沒有任何值被傳遞進來,就會使用default
的值作為預設值。1
2
3
4
5props:{
validationCounter:{
default: 2
}
}validationCounter
如果沒有任何值被傳進來,那麼validationCounter
的值就會是預設值數字2
。required
: 是否為必填項,如果是true
則該prop
沒有任何值被傳遞進來的話,就會出現錯誤提示。1
2
3
4
5props:{
validationCounter:{
required: true
}
}沒有傳值進去的話,會出現如下方的錯誤提示。
validator
: 自定義的驗證函式,會將該prop
的值當成唯一的引數帶入驗證該函式進行驗證。1
2
3
4
5
6
7
8props:{
validationCounter:{
validator(value) {
console.log(this)//window
return value >= 2;
}
}
}會將
validationCounter
的值作為引數帶入validator
參數進行驗證。驗證結果失敗的話,會出現如下方的提示。
特別注意
以上所使用的驗證方法,會是在該模組被創建以前就先進行驗證,所以不能再 default
或 validator
中使用模組的 data
或 computed
等屬性(因為模組還沒被創建,所以會找不到 data
或 computed
等屬性)。
有關 Vue 實例的創建可以參考DAY02 | 跟 Vue.js 認識的30天 - Vue 實體的生命週期(Lifecycle Hooks)及模板語法(Template Syntax)。
更多 props
的驗證可以參考Vue.js - props。
沒被模組定義的 Attribute
沒被模組定義的 Attribute,指的是在模組標籤上出現的 HTML Attribute ,卻沒在模組內的 props
註冊,出現這樣的 HTML Attribute 的話,會出現以下幾種可能性:
被添加到模板的根元素
1
2
3
4
5
6
7
8
9
10
11
12<non-prop-attribute data-ride="carousel"></non-prop-attribute>
<script>
</script>模組標籤的
data-ride="carousel"
會被加入到根元素<div id="carouselExampleSlidesOnly" class="carousel">
裡,在開發者工具可發現 DOM 會變成<div id="carouselExampleSlidesOnly" class="carousel" data-ride="carousel">
。class
或style
與根元素的class
或style
相結合1
2
3
4
5
6
7
8
9
10
11
12<non-prop-attribute data-ride="carousel" class="slide"></non-prop-attribute>
<script>
</script>模組標籤的
class="slide"
會與<div id="carouselExampleSlidesOnly" class="carousel">
中的class="carousel"
結合,變成<div id="carouselExampleSlidesOnly" class="carousel slide">
。取代根元素原有的 Attribute
1
2
3
4
5
6<non-prop-attribute-input type="date"></non-prop-attribute-input>
<script>
</script>模組標籤的
type="date"
會取代掉根元素中相同的屬性,所以<input type="text" class="form-control w-25" placeholder="我是input">
中的type="text"
會被type="date"
取代,變成<input type="date" class="form-control w-25" placeholder="我是input">
。
透過 inheritAttrs: false
取消根元素繼承未被定義的 prop
1 | <prop-inheritattrs label="我是label" placeholder="我是inheritAttrs後的input" required></prop-inheritattrs> |
被定義過的 prop label
不會受到任何影響,但是會發現未被定義的 HTML Attribute placeholder
跟 required
已經不會添加到根元素了。
加入 inheritAttrs: false
後:
未被定義的 HTML Attribute 除了不會被添加到根元素之外,也不會擁有取代的功能了,如上方(取代根元素原有的 Attribute)的例子,
type="text"
不會被模組標籤上的type="date"
所取代。class
跟style
不會受到影響,仍會被合併到根元素的class
跟style
上。
使用 inheritAttrs: false
跟 Vue 屬性 $attrs
,改變未被定義的 HTML Attribute的添加位置
1 | <prop-inheritattrs label="我是label" placeholder="我是inheritAttrs後的input" required class="custom-control-label"></prop-inheritattrs> |
加入 inheritAttrs: false
後, placeholder="我是inheritAttrs後的input" required
的確不會被添加到根元素了,但透過 Vue 指令 v-bind
跟屬性 $attrs
就可以將這些 Attribute 加入到 template
中的其他位置,如上方的 <input>
。但模組標籤中的 class
跟 style
還是會合併或添加到根元素的 class
跟 style
中。
Demo:
See the Pen [DAY12]跟 Vue.js 認識的30天 - Vue 模組資料傳遞(`props`) by Celeste6666 (@celeste6666) on CodePen.
參考資料: