筆者について
現フロントエンドエンジニア。
2018年2月当初、配列操作すらおぼつかなかった身から数ヶ月でwebpack覚えてbabel覚えてeslint覚えてVue + Vuex開発のメインプログラミングやってReact + Redux覚えてredux-actions覚えてredux-promise覚えてthunk覚えてrecompose覚えてmaterial-ui覚えてガリガリ開発している。しぬ。
お気持ち
vueでもStatelss Functional Componentライクに書きたい…書きたくない?
=> 今の自分ならこう書くかな、という例。他の案があれば見てみたいなあ。
ここに置いてあります
本題
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と同一です。
<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のみに注力します。
<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作成と言う形をとりました。
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);
}
}
};
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の多い構成になりました。
"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"
}
キリがないのでここまで!
ありがとうございます。