假如資料是從 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-list
的 source
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,就要考慮到其是最後執行,因此不能使用同步的方式思考