LoginSignup
0
3

More than 5 years have passed since last update.

ReactヲタがVueに手を出してみる#2

Posted at

前回からの続き

今回やること

前回スルーしてた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サーバ側で管理出来るようになりました。

actions.gif

まとめ

予想通りというか、APIコールのような非同期処理を行う場合もかなり簡単に実装することができた。Sagaなんていらんかったんや。
これでVue+Vuexの基礎的な要素は大体網羅できたと思うのでVueの勉強は一旦おしまいかな。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3