Promise + Prop 後寫法稍有不同

假如資料是從 App.vue 透過 API 抓取資料,然後透過 Prop 傳進 Component,最後再讀取 Prop 寫入 Component 的 Data,這看似平常的過程,若是同步資料則完全不是問題,但因為資料是從 API 來,為非同步 Promise,寫法則沒有想像中單純。

Version

Vue 2.5.17
Vue CLI 3.0.5

錯誤寫法


App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<div id="app">
<todo-list :source="todos">
</todo-list>
</div>
</template>

<script>
import TodoList from './components/todo-list.vue';
import { fetchTodos } from './api/todos.api';

const mounted = function() {
const response = res =>
this.todos = res.data.slice(0, 5);

fetchTodos()
.then(response);
};

const components = {
TodoList,
};

const data = function() {
return {
todos: [],
};
};

export default {
name: 'app',
components,
data,
mounted,
};
</script>


<style>
</style>

12 行

1
2
3
4
5
6
7
const mounted = function() {
const response = res =>
this.todos = res.data.slice(0, 5);

fetchTodos()
.then(response);
};

mounted hook 透過 API 抓取資料。

第 3 行

1
2
<todo-list :source="todos">
</todo-list>

todos 傳進 todo-listsource prop。

todo-list.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template>
<div id="todo-list">
<input type="text" v-model="input">
<button @click="addItem">Add</button>
<ul>
<li v-for="(todo, index) in todos" @click="finishItem(index)" :key="index">
{{ todo.title }}, {{ todo.completed }}
</li>
</ul>
</div>
</template>

<script>
const finishItem = function(index) {
this.todos[index].completed = !this.todos[index].completed;
};

const addItem = function() {
const elem = {
title: this.input,
completed: false,
};
this.todos = [...this.todos, elem];
};

const props = [
'source',
];

const data = function() {
return {
input: '',
todos: this.source,
};
};

const methods = {
finishItem,
addItem,
};

export default {
name: 'todo-list',
props,
data,
methods,
};
</script>


<style scoped>

</style>

30 行

1
2
3
4
5
6
const data = function() {
return {
input: '',
todos: this.source,
};
};

data()source prop 指定給 todos

這種寫法在若 prop 資料為同步,則為標準寫法;但若是非同步資料,則 todos 永遠為 []

因為 Promise 為非同步,會在同步執行完後才執行,也就是 todo-list component 的 data() 會先執行,最後才執行 Promise,因此 todos 永遠為 []

正確寫法


todo-list.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<template>
<div id="todo-list">
<input type="text" v-model="input">
<button @click="addItem">Add</button>
<ul>
<li v-for="(todo, index) in todos" @click="finishItem(index)" :key="index">
{{ todo.title }}, {{ todo.completed }}
</li>
</ul>
</div>
</template>

<script>
const finishItem = function(index) {
this.todos[index].completed = !this.todos[index].completed;
};

const addItem = function() {
const elem = {
title: this.input,
completed: false,
};
this.todos = [...this.todos, elem];
};

const source = function(value) {
this.todos = value;
};

const props = [
'source',
];

const data = function() {
return {
input: '',
todos: this.source,
};
};

const watch = {
source,
};

const methods = {
finishItem,
addItem,
};

export default {
name: 'todo-list',
props,
data,
watch,
methods,
};
</script>


<style scoped>

</style>

41 行

1
2
3
const watch = {
source,
};

由於 Promise 會最後執行,因此必須對 source prop 開 watch。

26 行

1
2
3
const source = function(value) {
this.todos = value;
};

將 Promise 最後執行改變 source prop 時,會執行 watch 的 source(),再由此 function 去改變 todos data。

如此 component 就能收到 prop 傳進來的非同步資料了。

Sample Code


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

Conclusion


  • 寫 ECMAScript 只要碰到非同步 Promise,就要考慮到其是最後執行,因此不能使用同步的方式思考
2018-10-23