LoginSignup
8
5

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-08-13

今まで「それReactでよくね?」ってVueを避けてたんだけど、そろそろ触ってみようと思います
※ 超初心者向けなのでVue有識者の方々には物足りない記事になってるかと思います

とりあえず触ってみるもの

Vue + Vuexの構成がReact + Redux的なデファクトスタンダードらしいのでそれを触ってみる
Nuxt.jsは素晴らしいらしいのだけど、それ使っちゃうと多分大事なところ全部Nuxtがやってくれちゃって自分で1から作れなくなるので今回はパス
代わりにvue-cliでwebpackを使ってみようと思ふ

作るもの

いつもの通り初回はTODOでも作ってみる

作ったものはこちら

インストール&環境構築

vue-cli

$ npm i -g vue-cli

OK.

Vue環境

$ vue-cli webpack vue-vuex-todo
? Project name vue-vuex-todo
? Project description A Vue.js project
? Author k-tada <white.rv0925@gmail.com>
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recommended) npm

   vue-cli · Generated "vue-vuex-todo".


# Installing project dependencies ...
  :
  :
  :
# Project initialization finished!
# ========================

To get started:

  cd vue-vuex-todo
  npm run dev

Documentation can be found at https://vuejs-templates.github.io/webpack

OK.

ちょこっと変更

2018/08/13現在、デフォルトだとbabel-preset-stage-2が入るみたいだけど、stage-3のObject Spread使いたいから面倒くさいからstage-0入れる

$ npm i -D babel-preset-stage-0

んで.babelrc書き換える

.babelrc
  {
    "presets": [
      ["env", {
        "modules": false,
        "targets": {
          "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
        }
      }],
-     "stage-2"
+     "stage-0"
    ],
    "plugins": ["transform-vue-jsx", "transform-runtime"],
    "env": {
      "test": {
-       "presets": ["env", "stage-2"],
+       "presets": ["env", "stage-0"],
        "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
      }
    }
  }

OK.

動確

ここで一度確認

$ npm run dev

でブラウザからhttp://localhost:8080/にアクセスしたら下記の画面が見えるはず
スクリーンショット 2018-08-13 19.34.49.png

OK.

Vue作っていく

Vueだけなら、基本はsrc/componentsの下に.vueファイルを作っていけばOKっぽい

今回のメインの目的はVueに触ることなので、デザインあまり考えたくなかったからTodoMVCっぽい感じで作ることにした。

VueのコンポーネントはReactと違ってJS in HTMLなので、下記のような感じで作ればいいっぽい。
コンポーネント設計のセンスは生まれつき持ち合わせて無いのでご愛嬌

src/App.vue
<template>
  <div class="app">
    <div class="container">
      <Logo />
      <TodoApp />
    </div>
  </div>
</template>

<script>
import Logo from './components/Logo'
import TodoApp from './components/TodoApp'

export default {
  name: 'App',
  components: {
    Logo,
    TodoApp
  }
}
</script>

<style scoped>
/* 省略 */
</style>

他のコンポーネントもさっくりと

src/components/Logo.vue
<template>
  <div class="logo">{{text}}</div>
</template>

<script>
export default {
  name: 'Logo',
  data () {
    return {
      text: 'todos'
    }
  }
}
</script>

<style scoped>
/* 省略 */
</style>
src/components/TodoApp.vue
<template>
  <div class="container">
    <Form />
    <List />
  </div>
</template>

<script>
import Form from './todo/Form'
import List from './todo/List'

export default {
  name: 'TodoApp',
  components: {
    Form,
    List
  }
}
</script>

<style scoped>
/* 省略 */
</style>
src/components/todo/Form.vue
<template>
  <div class="form">
    <div class="toggle-all" />
    <div class="input">
      <input type="text" class="new" placeholder="What needs to be done?" />
    </div>
  </div>
</template>

<script>
export default {
  name: 'Form'
}
</script>

<style scoped>
/* 省略 */
</style>
src/components/todo/List.vue
<template>
  <div class="todos">
    <div class="todo" v-for="todo in todos" :key="todo.id">
      <input
        class="toggle"
        type="checkbox"
        :checked="todo.isDone"
      />
      <div class="text">{{todo.text}}</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'List',
  data () {
    /* test data */
    return {
      todos: [
        { id: 1, text: 'aaa', isDone: false },
        { id: 2, text: 'bbb', isDone: true },
        { id: 3, text: 'ccc', isDone: false }
      ]
    }
  }
}
</script>

<style scoped>
/* 省略 */
</style>

これでこうなる
スクリーンショット 2018-08-13 19.49.10.png

コミットはこちら
https://github.com/k-tada/vue-vuex-todo/commit/2e0143784cba9cdedc818d1eafadb57bbbaaccd9

Vuex作ってく

Vuexの詳細な説明は他の記事を参照(なげやり)

インストール

vue-cli webpackで作った環境にはvuexは入ってないので自前でインストールする

$ npm i -S vuex

vuex store作る

ReduxではAction,ActionCreator,Reducerと色々用意しないといけないけど、VuexだとStore1つでOK。Ducksパターンっぽい

ReduxのcombineReducerっぽく複数のStoreを扱えるようにしとく。

src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import todos from './todos'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    todos
  }
})

続いてToDO管理用のStore

src/store/todos.js
export default {
  namespaced: true,
  state: {
    nextId: 1,
    todos: [],
    isDoneAll: false
  },
  getters: {
    allTodos (state) {
      return state.todos
    },
    activeTodos (state) {
      return state.todos.filter(t => !t.isDone)
    },
    completedTodos (state) {
      return state.todos.filter(t => t.isDone)
    }
  },
  mutations: {
    addTodo (state, todo) {
      state.todos.push({ id: state.nextId, text: todo, isDone: false })
      state.nextId += 1
    },
    toggleTodo (state, id) {
      state.todos = state.todos.map(t => ({ ...t, isDone: t.id === id ? !t.isDone : t.isDone }))
      state.isDoneAll = state.todos.every(t => t.isDone)
    },
    toggleAllTodo (state, isDone) {
      state.todos = state.todos.map(t => ({ ...t, isDone }))
      state.isDoneAll = isDone
    },
    deleteTodo (state, id) {
      state.todos = state.todos.filter(t => t.id !== id)
    }
  },
  actions: {}
}

順に説明するとこんな感じ。

  • namespaced: true
    Storeごとに名前空間をもたせられる。combineReducerされたStateっぽい感じになる。
  • state
    Storeの持つステート。ReduxのStateと(多分)同じ。
  • getters
    ステートから値を取り出すためのメソッド。別のStoreのステートも引っ張ってこれるっぽい。Reselect的なアレ。gettersを経由しなくても直接ステートを参照することも出来るのでステートから何かしら処理したデータをVue側で参照したいときに使う。
  • mutations
    ActionCreator + Reducer的な。ステートを更新するためのメソッド。基本的にはステートの更新は必ずmutations経由で行う。Viewから直で呼べる。
  • actions
    非同期処理部分。今は使ってないから空。ToDOをサーバから取得するような処理を書く場合はここに書くのかな。actionsでAPIコールしてmutationsを呼び出してステートを更新するイメージだと思う

Vueから呼び出す

まずはList

src/components/todo/List.vue
  <template>
    <div class="todos">
      <div class="todo" v-for="todo in todos" :key="todo.id">
        <input
          class="toggle"
          type="checkbox"
          :checked="todo.isDone"
+         @click="toggleTodo(todo.id)"
        />
        <div class="text">{{todo.text}}</div>
      </div>
    </div>
  </template>

  <script>
+ import { mapGetters } from 'vuex'

  export default {
    name: 'List',
-   data () {
-     /* test data */
-     return {
-       todos: [
-         { id: 1, text: 'aaa', isDone: false },
-         { id: 2, text: 'bbb', isDone: true },
-         { id: 3, text: 'ccc', isDone: false }
-       ]
-     }
-   }
+   computed: mapGetters('todos', {
+     todos: 'allTodos'
+   }),
+   methods: {
+     toggleTodo (id) {
+       this.$store.commit('todos/toggleTodo', id)
+     }
+   }
  }
  </script>

ポイントはこの辺。
- @clickでクリック時のイベントをbindする(v-onの省略記法らしい)
- vuexのmapGettersで、vueに必要なデータを取り出す
- this.$store.commitで、vuexのmutationsを呼び出す

続いてForm

src/components/todo/Form
  <template>
    <div class="form">
-     <div class="toggle-all" />
+     <div class="toggle-all" @click="toggleAllTodo" />
      <div class="input">
-       <input type="text" class="new" placeholder="What needs to be done?" />
+       <input
+         type="text"
+         class="new"
+         placeholder="What needs to be done?"
+         :value="text"
+         @input="updateText($event)"
+         @keyup.enter="addTodo()"
+       />
      </div>
    </div>
  </template>

  <script>
+ import { mapState } 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)
+       this.text = ''
+     },
+     toggleAllTodo () {
+       this.$store.commit('todos/toggleAllTodo', !this.isDoneAll)
+     }
+   }
  }
  </script>

色々差分出てるけど、ポイントはこの辺

  • 入力フォームのinputイベントでVueのローカルステートを更新(updateText
  • 入力フォームのEnterキー押下イベントでVuexのaddTodomutationを呼び出し
  • toggleAll系の処理(詳細は割愛)

最後にVueからVuexのStoreを見れるようにmain.jsを修正

src/main.js
  import Vue from 'vue'
  import App from './App'
+ import store from './store'

  Vue.config.productionTip = false

  /* eslint-disable no-new */
  new Vue({
    el: '#app',
+   store,
    components: { App },
    template: '<App/>'
  })

これでOK。こんな感じで動く
todo.gif

Delete忘れてた(2018/08/14)

src/components/todo/Form
  <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()"
        />
+       <div class="destroy" @click="deleteTodo(todo.id)">×</div>
      </div>
    </div>
  </template>

  <script>
  import { mapState } 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)
        this.text = ''
      },
      toggleAllTodo () {
        this.$store.commit('todos/toggleAllTodo', !this.isDoneAll)
+     },
+     deleteTodo (id) {
+       this.$store.commit('todos/deleteTodo', id)
      }
    }
  }
  </script>

これでOKね.
deletetodo.gif

まとめ

まだVueの書き方には慣れないけど、ざっと書いてみた感じだとReact + Reduxに比べて楽に書けるように感じた。
おそらく中〜大規模なアプリケーションになると、Redux-sagaのような仕組みが欲しくなると思うけど、軽量アプリケーションならぶっちゃけReact + Reduxを選ぶよりVue + Vuexのほうが楽かもしれない。React + MobXともいい勝負かも。

続くかも。

8
5
2

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
8
5