將 Data 傳進 Component

Component 若只有一層,那問題不大,若牽涉到 component 包含 component,就會牽涉到一個基本問題:該如何將 data 由外層 component 傳給內層 component ?

這時候就要使用 Vue Component 的 Prop,這也是 Vue 從 React 學來的部分。

Version


Vue 2.5.17

Static Prop


一樣是老梗 Hello World,但這次 data 並不是在 Vue Instance 內定義,而是由外層傳進來。

靜態資料 由 prop 傳進 component

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Static Prop</title>
</head>
<body>
<div id="app">
<hello-world greeting="Hello" user-name="World"></hello-world>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="HelloWorld.js"></script>
<script src="index.js"></script>
</html>

第 9 行

1
<hello-world greeting="Hello" user-name="World"></hello-world>

一樣使用 <hello-world></hello-world> Component,但這次透過自訂的 greetinguser-name 將 data 由 component 的外層傳進來。

index.js

1
2
3
new Vue({
el: '#app'
});

建立 Vue Instance。

HelloWorld.js

1
2
3
4
5
6
7
8
9
Vue.component('HelloWorld', {
props: [
'greeting',
'userName'
],
template: `
<span>{{ greeting }} {{ userName }}</span>
`,

});

第 2 行

1
2
3
4
props: [
'greeting',
'userName',
],

若要由 Component 的外層傳 data 進來,則要在 Vue Instance 使用 props property。

props 為 array,每個 prop 使用 string 定義。

第 6 行

1
2
3
template: `
<span>{{ greeting }} {{ userName }}</span>
`,

props 宣告過 greetinguserName prop 之後,就可在 template 如同使用 data 的 model 了。

在 prop 使用上,有一點要注意:

  • Props 若在 JavaScript 使用了 CamelCase,在 Vue Template 會自動改成 kebab-case 才能用 (W3C 建議)

Vue Template 能綁定的資料,除了能使用 datacomputed 外,目前還多了 props

Dynamic Prop


剛剛傳進 prop 的值是寫死的,能夠動態以 MVVM 的 model 結合 prop 嗎 ?

動態資料 (MVVM 的 model ) 傳進 prop

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic Prop</title>
</head>
<body>
<div id="app">
<ul>
<hello-world v-for="userName in userNames" greeting="Hello" :user-name="userName"></hello-world>
</ul>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="HelloWorld.js"></script>
<script src="index.js"></script>
</html>

第 9 行

1
2
3
<ul>
<hello-world v-for="userName in userNames" greeting="Hello" :user-name="userName"></hello-world>
</ul>

想將 userName 綁定到 user-name prop,必須使用 : Attribute Binding。

userName 則由 v-for 來自於 userNames

index.js

1
2
3
4
5
6
7
8
9
10
new Vue({
el: '#app',
data: {
userNames: [
'Sam',
'Kevin',
'John'
]
}
});

userNames 是 Vue Instance 的 data 所定義的 model。

HelloWorld.js

1
2
3
4
5
6
7
8
9
Vue.component('HelloWorld', {
props: [
'greeting',
'userName'
],
template: `
<li>{{ greeting }} {{ userName }}</li>
`

});

與 Static Prop 的 HelloWorld.js 完全一樣。

透過 Dynamic Prop,我們就能將 component 外層的 model 透過 prop 傳進 component

Unidirectional Dataflow


Vue Prop 是 單向數據流,也就是 data 只能從 component 外層傳進 component,而無法由內層 component 傳到外層 component。

若要將 data 從內層 component 傳到外層 component,則要使用 event

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unidirectional Dataflow (Warning)</title>
</head>
<body>
<div id="app">
{{ counter }}
<button @click="add">+</button>
<my-counter :inner-counter="counter"></my-counter>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="Counter.js"></script>
<script src="index.js"></script>
</html>

第 9 行

1
2
{{ counter }}
<button @click="add">+</button>

外層有自己 counter 計算。

11 行

1
<my-counter :inner-counter="counter"></my-counter>

想要將 counter model 透過 inner-counter prop 傳進 MyCounter component。

index.js

1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
add() {
this.counter++;
}
}
});

第 3 行

1
2
3
4
5
6
7
8
data: {
counter: 0
},
methods: {
add() {
this.counter++;
}
}

上層有自己的 counter 計算。

Counter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('MyCounter', {
template: `
<div>
<h2>{{ innerCounter }}</h2>
<button @click="add">+</button>
</div>
`,

props: [
'innerCounter'
],
methods: {
add() {
this.innerCounter++;
}
}
});

第 8 行

1
2
3
props: [
'innerCounter'
],

定義 innerCounter prop。

11 行

1
2
3
4
5
methods: {
add() {
this.innerCounter++;
}
}

直接 innterCounter prop 做累加。

prop000

這種寫法雖然可以執行,但有個淺在問題:由於 Vue 的 Unidirectional Dataflow,每次 component 外層的 model 改變,都會傳入內層 component,因而會覆蓋掉原本 component 的資料。

Vue 也在 run-time 提出警告,建議不要對 prop 直接修改,因為 data 隨時會被外層 component 蓋掉。

由於不建議直接改 prop,Vue 官網建議兩種 Pattern 使用 prop:

  • 改用 Data
  • 改用 Computed

改用 Data

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unidirectional Dataflow (Data)</title>
</head>
<body>
<div id="app">
{{ counter }}
<button @click="add">+</button>
<p></p>
<my-counter :start-counter="counter"></my-counter>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="Counter.js"></script>
<script src="index.js"></script>
</html>

12 行

1
<my-counter :start-counter="counter"></my-counter>

inner-counter 改成 start-counter prop。

index.js

1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
add() {
this.counter++;
}
}
});

與之前的 index.js 完全一樣。

Counter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Vue.component('MyCounter', {
template: `
<div>
<h2>{{ innerCounter }}</h2>
<button @click="add">+</button>
</div>
`,

props: [
'startCounter'
],
data() {
return {
innerCounter: this.startCounter
}
},
methods: {
add() {
this.innerCounter++;
}
}
});

第 8 行

1
2
3
props: [
'startCounter'
],

改成 startCounter prop。

11 行

1
2
3
4
5
data() {
return {
innerCounter: this.startCounter
}
},

由於 prop 不適合直接修改,因此我們另外宣告了 innerCounter model。

startCounter 的用途只在於初始化 innerCounter model。

由原本直接修改 prop,改成修改 Data,prop 只退化用來初始化 model

改用 Computed

使用 Data 的優點內層 component 的資料不會受外層影響,但缺點是內層 component 的資料不會與外層連動。

若有內外層 data 連動的需求,Vue 則建議改用 computed。

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unidirectional Dataflow (Computed)</title>
</head>
<body>
<div id="app">
{{ counter }}
<button @click="add">+</button>
<my-counter :start-counter="counter"></my-counter>
</div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="Counter.js"></script>
<script src="index.js"></script>
</html>

11 行

1
<my-counter :start-counter="counter"></my-counter>

一樣使用 start-counter prop 傳入資料。

index.js

1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: '#app',
data: {
counter: 0
},
methods: {
add() {
this.counter++;
}
}
});

index.js 一樣也沒變。

Counter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vue.component('MyCounter', {
template: `
<div>
<h2>{{ countDoubled }}</h2>
</div>
`,

props: [
'startCounter'
],
computed: {
countDoubled() {
return this.startCounter * 2;
}
}
});

10 行

1
2
3
4
5
computed: {
countDoubled() {
return this.startCounter * 2;
}
}

由原本的 innerCounter 改成 countDoubled()

第 2 行

1
2
3
4
5
template: `
<div>
<h2>{{ countDoubled }}</h2>
</div>
`,

Data Binding 改成 countDoubled()

如此內層 component 的 data 就與外層同步

2 種寫法都各有適用場景,視需求決定要使用

Conclusion


  • 透過 prop,data 能由外層傳進 Component
  • Dynamic Prop 能將 MVVM 的 model 也綁定 prop,不再只是寫死的資料
  • Prop 只支援 Unidirectional Dataflow,也就是外層的 data 改變,會影響到內層 component,但內層 component 的 data 改變,不會影響到外層 component
  • 若要內層 component 的 data 改變,會影響到外層 component,就必須使用 event

Sample Code


完整的範例可以在我的 GitHub 上找到

Reference


Vue, Props

2018-11-06