前回からの続き
今回やること
前回スルーしてたVuexのactionsに触れていきたい
APIサーバの準備
今回やりたいことから外れてるので、APIサーバはjson-serverで簡単に用意する
まずはインストール
$ npm i -D json-server
んでjsonファイルの作成
db.json
{ "todos": [] }
最後にスクリプトを設定して
package.json
"scripts": {
:
:
+ "server": "json-server db.json --port 3333"
}
npm run server
で起動
$ npm run server
> vue-vuex-todo@1.0.0 server /Users/tada/work/workshop/git/vue-vuex-todo
> json-server db.json --port 3333
\{^_^}/ hi!
Loading db.json
Done
Resources
http://localhost:3333/todos
Home
http://localhost:3333
Type s + enter at any time to create a snapshot of the database
$ curl localhost:3333/todos
[]
OK.
APIにアクセスするActionを作る
APIコールにはaxiosを使ってます。
npm i -S axios
Action作る過程はバッサリカット。こんな感じ
src/store/todos.js
// 使わないmutationsは削除済み
mutations: {
incrementId (state) {
state.nextId += 1
},
setTodos (state, todos) {
// TODO ここダサいのでどうにか死体
state.todos = Array.isArray(todos) && todos.length > 0 ? todos.map(t => ({ ...t, isDone: JSON.parse(t.isDone) })) : []
state.nextId = Array.isArray(todos) && todos.length > 0 ? Math.max(...todos.map(t => +t.id)) + 1 : 0
state.isDoneAll = state.todos.every(t => t.isDone)
}
},
actions: {
async getTodos ({ commit }) {
try {
commit('setTodos', (await axios.get('/todos')).data)
} catch (e) {
console.err('Failed to get todos', e.message)
}
},
async addTodo ({ commit, dispatch, state }, text) {
const todo = { id: state.nextId, text, isDone: false }
commit('incrementId')
try {
await axios.post('/todos', todo)
dispatch('getTodos')
} catch (e) {
console.err('Failed to add todo', e.message)
}
},
async toggleTodo ({ commit, dispatch, state }, id) {
const target = state.todos.find(t => t.id === id)
if (!target) {
console.log(`Todo[${id}] is not found`)
return
}
try {
await axios.put(`/todos/${id}`, { ...target, isDone: !target.isDone })
dispatch('getTodos')
} catch (e) {
console.err('Failed to toggle todo', e.message)
}
},
async toggleAllTodo ({ commit, dispatch, state }) {
try {
await Promise.all(
state.todos.map(async t => axios.put(`/todos/${t.id}`, { ...t, isDone: !state.isDoneAll }))
)
dispatch('getTodos')
} catch (e) {
console.err('Failed to toggle todo', e.message)
}
},
async deleteTodo ({ commit, dispatch, state }, id) {
const target = state.todos.find(t => t.id === id)
if (!target) {
console.log(`Todo[${id}] is not found`)
return
}
try {
await axios.delete(`/todos/${id}`)
dispatch('getTodos')
} catch (e) {
console.err('Failed to delete todo', e.message)
}
}
}
簡単に説明するとAction
では基本非同期処理等を行う、Mutationをコミットするという2つのことを行う。Reduxでいうsagaやthunk等のmiddleware層が行うような処理が該当する。と思われる。
Action
は引数にコンテキストを受け取るようになっていて、コンテキストには下記のパラメータが含まれる
- commit
Mutationを発行するためのメソッド - dispatch
他のActionを発行するためのメソッド - state
現在のステート
ViewからActionを呼び出す
さっくりとこんな感じ
src/components/todo/List.vue
<script>
- import { mapGetters } from 'vuex'
+ import { mapGetters, mapActions } from 'vuex'
export default {
name: 'List',
computed: mapGetters('todos', {
todos: 'allTodos'
}),
methods: {
- toggleTodo (id) {
- this.$store.commit('todos/toggleTodo', id)
- },
- deleteTodo (id) {
- this.$store.commit('todos/deleteTodo', id)
- }
+ ...mapActions({
+ toggleTodo: 'todos/toggleTodo',
+ deleteTodo: 'todos/deleteTodo'
+ })
}
}
</script>
Actionを呼び出すにはVuexのmapActions
を使えばいいっぽい。それだけ。
他のファイルも差分だけ残しとく。
src/components/todo/Form.vue
<template>
<div class="form">
<div class="toggle-all" @click="toggleAllTodo" />
<div class="input">
<input
type="text"
class="new"
placeholder="What needs to be done?"
:value="text"
@input="updateText($event)"
- @keyup.enter="addTodo()"
+ @keyup.enter="addTodoAndClearText()"
/>
</div>
</div>
</template>
<script>
- import { mapState } from 'vuex'
+ import { mapState, mapActions } from 'vuex'
export default {
name: 'Form',
data () {
return {
text: ''
}
},
computed: mapState({
isDoneAll: state => state.todos.isDoneAll
}),
methods: {
updateText (e) {
this.text = e.target.value
},
- addTodo (e) {
- this.$store.commit('todos/addTodo', this.text)
+ addTodoAndClearText () {
+ this.addTodo(this.text)
this.text = ''
},
- toggleAllTodo () {
- this.$store.commit('todos/toggleAllTodo', !this.isDoneAll)
- }
+ ...mapActions({
+ addTodo: 'todos/addTodo',
+ toggleAllTodo: 'todos/toggleAllTodo'
+ })
}
}
</script>
初回読み込み時に一覧を取得する処理を追加して完了
src/components/TodoApp.vue
<template>
<div class="container">
<Form />
<List />
</div>
</template>
<script>
+ import { mapActions } from 'vuex'
import Form from './todo/Form'
import List from './todo/List'
export default {
name: 'TodoApp',
+ mounted () {
+ this.getTodos()
+ },
components: {
Form,
List
- }
+ },
+ methods: mapActions({
+ getTodos: 'todos/getTodos'
+ })
}
</script>
これでToDOリストをAPIサーバ側で管理出来るようになりました。
まとめ
予想通りというか、APIコールのような非同期処理を行う場合もかなり簡単に実装することができた。Sagaなんていらんかったんや。
これでVue+Vuexの基礎的な要素は大体網羅できたと思うのでVueの勉強は一旦おしまいかな。