Tour of HeroesをVue+Vuexで再実装した
Vue+Vuexを勉強したアウトプットとしてアプリを作りました。
hi1280.hatenablog.com
作ったもの
Tour of HeroesはAngular公式のチュートリアルです。
複数画面があって、WebAPIを呼び出すサンプルになっています。
今回はこのTour of Heroesを題材にして、Vue+Vuexを使ってTour of Heroesを再実装しました。
実際のプログラムはこちらです。
github.com
Angular、React+Reduxと比較しつつ、Vue+Vuexによる実装内容を説明します。
Viewの作成
リスト形式でデータを表示する
VueではHTMLに対してテンプレート構文を書いて動的にHTMLを生成できるようになっています。
v-for
というテンプレート構文で繰り返しの記述をサポートしています。
コードを見てもらえば分かるようにAngularにかなり似ています。
Dashboard.vue
<!-- 一部抜粋 --> <router-link v-for="hero in sliced_heroes" :to="{ name: 'detail', params: {id: hero.id} }" class="col-1-4" :key="hero.id"> <div class="module hero"> <h4>{{hero.name}}</h4> </div> </router-link>
ちなみにAngularの場合はngFor
というテンプレート構文になります。
dashboard.component.html
<!-- 一部抜粋 --> <a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}"> <div class="module hero"> <h4>{{hero.name}}</h4> </div> </a>
フォームを作成する
v-model
というテンプレート構文で双方向データバインディングが可能です。
バインディングするデータの元はVuexで定義したStoreです。
HeroDetail.vue
// 一部抜粋 <template> <div v-if="hero"> <h2>{{hero.name}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label>name: <input v-model="hero.name" placeholder="name"/> </label> </div> <button @click="goBack()">go back</button> <button @click="save()">save</button> </div> </template> <script> export default { data() { return { hero: { id: 0, name: '' } } }, created() { this.$store.dispatch('fetchHero', this.$route.params.id); this.hero = this.$store.getters.heroes.find(element => element.id == this.$route.params.id); }, methods: { goBack() { this.$router.go(-1); }, save() { this.$store.dispatch('updateHero', this.hero); this.$router.go(-1); } } } </script>
Angularのチュートリアルでも双方向データバインディングでフィールドの値とフォームの値とを同期しています。
hero-detail.component.html
<!-- 一部抜粋 --> <input [(ngModel)]="hero.name" placeholder="name"/>
hero-detail.component.ts
// 一部抜粋 export class HeroDetailComponent implements OnInit { @Input() hero: Hero; }
WebAPI呼び出し
VuexのActionでWebAPIを呼び出します。
HTTPのリクエストを実行するのにvue-resource
を使いました。
なお、vue-resource
は公式で推奨するやり方ではなくなっているようです。
modules/heroes.js
// 一部抜粋 const actions = { fetchHeroes: ({commit}) => { Vue.http.get(`?key=${key}`) .then(response => response.json()) .then(heroes => { if(heroes) { commit('FETCH_HEROES', heroes); } }); },
Actionでの非同期処理の結果をMutationで処理します。
modules/heroes.js
// 一部抜粋 const mutations = { 'FETCH_HEROES' (state, heroes) { state.heroes = heroes; },
ちなみにReduxのコードです。
ActionではVuexとは違い、非同期処理によるPromiseオブジェクトをそのまま渡しています。
actions/index.js
// 一部抜粋 export function fetchHeroes() { const request = axios.get(`${ROOT_URL}?key=${key}`); return { type: FETCH_HEROES, payload: request } }
ActionからReducerに処理が流れる時に、redux-promise
を使って、PromiseのオブジェクトをJavaScriptのオブジェクトに変換しています。
ReducerではPromiseの値であることを意識せずに処理します。
reducer-heroes.js
// 一部抜粋 export default function(state = {}, action) { switch(action.type) { case DELETE_HERO: return _.omit(state, action.payload); case FETCH_HEROES: return _.mapKeys(action.payload.data, 'id'); case CREATE_HERO: case FETCH_HERO: case UPDATE_HERO: return {...state, [action.payload.data.id]: action.payload.data}; default: return state; } }
CSS指定
グローバルなCSSやScopedなCSSを記述することが可能です。
以下のようにしてコンポーネント内で閉じたCSS指定が可能です。
App.vue
/* 一部抜粋 */ <style scoped> h1 { font-size: 1.2em; color: #999; margin-bottom: 0; } h2 { font-size: 2em; margin-top: 0; padding-top: 0; } nav a { padding: 5px 10px; text-decoration: none; margin-top: 10px; display: inline-block; background-color: #eee; border-radius: 4px; } nav a:visited, a:link { color: #607D8B; } nav a:hover { color: #039be5; background-color: #CFD8DC; } nav a.active { color: #039be5; } </style>
Angularでも同様にコンポーネント内に閉じたCSS指定が可能です。
まとめ
今回取り組んでみて、Vue+VuexではAngularやReduxの良いところを取り入れている感じがしました。
ActionやMutationの部分など、VuexはReduxに比べて作りがシンプルな感じがしました。