如何將非同步資料傳入 Prop ?
假如資料是從App.vue
透過 API 抓取資料,然後透過 Prop 傳進 Component,最後再讀取 Prop 寫入 Component 的 Data,這看似平常的過程,若是同步資料則完全不是問題,但因為資料是從 API來,為非同步 Promise,寫法則沒有想像中單純。
Version
Vue 2.5.17
Vue CLI 3.0.5
錯誤寫法
App.vue
<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 行
const mounted = function() { const response = res => this.todos = res.data.slice(0, 5); fetchTodos() .then(response); };
在mounted
hook 透過 API 抓取資料。
第 3 行
<todo-list :source="todos"> </todo-list>
將todos
傳進todo-list
的source
prop。
todo-list.vue
<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 行
const data = function() { return { input: '', todos: this.source, }; };
在data()
將source
prop 指定給todos
。
這種寫法在若 prop 資料為同步,則為標準寫法;但若是非同步資料,則todos
永遠為[]
。
因為 Promise 為非同步,會在同步執行完後才執行,也就是todo-list
component 的data()
會先執行,最後才執行 Promise,因此todos
永遠為[]
。
正確寫法
todo-list.vue
<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 行
const watch = { source, };
由於 Promise 會最後執行,因此必須對source
prop 開 watch。
26 行
const source = function(value) { this.todos = value; };
將 Promise 最後執行改變source
prop 時,會執行 watch 的source()
,再由此 function 去改變todos
data。
如此 component 就能收到 prop 傳進來的非同步資料了。
Conclusion
- 寫 ECMAScript 只要碰到非同步 Promise,就要考慮到其是最後執行,因此不能使用同步的方式思考