React + Reduxを経験してからのstatelessなVue + Vuex構成について

# 筆者について
現フロントエンドエンジニア。
2018年2月当初、配列操作すらおぼつかなかった身から数ヶ月でwebpack覚えてbabel覚えてeslint覚えてVue + Vuex開発のメインプログラミングやってReact + Redux覚えてredux-actions覚えてredux-promise覚えてthunk覚えてrecompose覚えてmaterial-ui覚えてガリガリ開発している。しぬ。

## お気持ち
vueでもStatelss Functional Componentライクに書きたい…書きたくない？
=> 今の自分ならこう書くかな、という例。他の案があれば見てみたいなあ。

## ここに置いてあります
https://github.com/masakitm/vue-vuex-router-playground

## 本題
React + Reduxでの開発経験を生かし、Stateless Functional Componentライクな書き方を採用したVue + Vuex + Routerの環境です。 
```master```ブランチにはサンプルとしてTodoアプリを置いています。 

## アプリケーションの構成について
厳密ではありませんが、Atomic Designを採用しています。
components/pagesに対してcontainerコンポーネントを1対1で作成し、vuexと紐付け、子コンポーネントへpropsをリレーする構成としました。
vuexを採用しているのはrouterでの遷移時にデータを保持するためです。

```
src  
  * index.js
  * -components   
      - atoms
      - molecules
      - organisms
      - templates
      - pages
  * -containers  
  * -routes
  * -store
```

## Container層
責務を分割しロジック部分のみを担保します。思想自体はReact + ReduxのContainerと同一です。

```vue:container

<template>
  <Content 
    :todos="todos"
    :temp="temp"
    :handleSubmit="addTodo"
    :handleKeyUp="updateTemp"
    :alreadyExists="alreadyExists"
  />
</template>

<script>
import { mapState, mapActions, mapMutations, mapGetters } from 'vuex';
import Content from '~/components/pages/Todo.vue';

export default {
  computed: {
    ...mapState({
      todos: state => state.todoStore.todos,
      temp: state => state.todoStore.temp
    }),
    ...mapGetters(['alreadyExists'])
  },
  methods: {
    ...mapActions(['addTodo']),
    ...mapMutations(['updateTemp'])
  },
  components: {
    Content
  }
};
</script>
```

## Components層

責務を分割しviewの部分のみを担保します。
Stateless Functional Componentを強く意識し、各コンポーネントにdata（reactで言うstate）は持たせません。
Container層から渡ったpropsを元にviewのみに注力します。

```vue:components
<template>
  <div>
    <h2>TodoApp</h2>
    <TodoForm 
      :handleKeyUp="handleKeyUp"
      :handleSubmit="handleSubmit"
    />
    
    <Error v-if="alreadyExists" />

    <TodoList 
      :todos="todos"
    />
  </div>
</template>

<script>
import Error from '~/components/atoms/Error.vue';
import TodoForm from '~/components/organisms/TodoForm.vue';
import TodoList from '~/components/organisms/TodoList.vue';

export default {
  props: {
    handleSubmit: Function,
    handleKeyUp: Function,
    todos: Array,
    alreadyExists: Boolean
  },
  components: {
    Error,
    TodoForm,
    TodoList
  }
};
</script>
```

## storeについて
combineReducerのイメージでvuexのmoduleを活用します。
好みですが、reduxではactionCreatorでガッチリとpayloadを作成、reducerは愚直にpayloadをstateに反映するのみとしたかったため、vuexにおいてもmutationsはstate反映のみ、actionsでpayload作成と言う形をとりました。

```javascript:todoStore
let id = 0;

export default {
  state: {
    temp: '',
    todos: []
  },
  mutations: {
    updateTodos: (state, payload) => (state.todos = payload),
    updateTemp: (state, payload) => (state.temp = payload)
  },
  actions: {
    addTodo: ({ commit, state, getters }, callback) => {
      const newItem = {
        name: state.temp,
        done: false,
        id: id + 1
      };
      const newState = state.todos.concat(newItem);

      if (!getters.alreadyExists) {
        commit('updateTodos', newState);
        commit('updateTemp', '');
      }
      callback && callback();
    }
  },
  getters: {
    alreadyExists: state => {
      const { todos, temp } = { ...state };
      return todos.some(todo => todo.name === temp);
    }
  }
};

```

```javascript:store
import Vue from 'vue';
import Vuex from 'vuex';
import todoStore from '~/store/todoStore';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    todoStore
  }
});

export default store;
```

## 採用パッケージについて
今回はvue-cliを使わず、よく使うreact用テンプレートに近づけました。eslint + prettier連携や.vue内のlintも採用しています。    
styled-components採用型React + Reduxに比べloaderの多い構成になりました。

```json:package.json
"devDependencies": {
  "babel-cli": "^6.26.0",
  "babel-core": "^6.26.3",
  "babel-eslint": "^8.2.6",
  "babel-loader": "^7.1.5",
  "babel-plugin-root-import": "^6.1.0",
  "babel-plugin-transform-object-rest-spread": "^6.26.0",
  "babel-preset-env": "^1.7.0",
  "css-loader": "^1.0.0",
  "eslint": "^5.2.0",
  "eslint-config-prettier": "^2.9.0",
  "eslint-config-vue": "^2.0.2",
  "eslint-import-resolver-babel-plugin-root-import": "^1.1.1",
  "eslint-plugin-import": "^2.13.0",
  "eslint-plugin-node": "^7.0.1",
  "eslint-plugin-prettier": "^2.6.2",
  "eslint-plugin-promise": "^3.8.0",
  "eslint-plugin-standard": "^3.1.0",
  "eslint-plugin-vue": "^5.0.0-beta.1",
  "html-loader": "^0.5.5",
  "node-sass": "^4.9.2",
  "prettier": "^1.14.0",
  "sass-loader": "^7.0.3",
  "style-loader": "^0.21.0",
  "vue-html-loader": "^1.2.4",
  "vue-loader": "^15.2.6",
  "vue-style-loader": "^4.1.1",
  "vue-template-compiler": "^2.5.16",
  "webpack": "^4.16.3",
  "webpack-cli": "^3.1.0",
  "webpack-dev-server": "^3.1.5"
},
"dependencies": {
  "babel-polyfill": "^6.26.0",
  "vue": "^2.5.16",
  "vue-router": "^3.0.1",
  "vuex": "^3.0.1"
}
```

キリがないのでここまで！
ありがとうございます。