vueを使ってhackernewsを表示するチュートリアルを以前やったことがあったので
それをまとめます。
src/store.js
import Vue from 'vue';
import Vuex from 'vuex';
import types from './types';
Vue.use(Vuex);
const BASE_URL = 'https://api.hackernews.io';
export default new Vuex.Store({
state: {
newsItems: [],
currentNewsItem: {},
loading: false,
},
mutations: {
[types.SET_NEWS_ITEMS](state, newsItems) {
state.newsItems = newsItems;
},
[types.SET_CURRENT_NEWS_ITEM](state, newsItem) {
state.currentNewsItem = newsItem;
},
[types.SET_LOADING](state, loading) {
state.loading = loading;
},
[types.APPEND_NEWS_ITEMS](state, newsItems) {
const uniqueIds = {};
state.newsItems = state.newsItems.concat(newsItems).filter((item) => {
if (!uniqueIds[item.id]) {
uniqueIds[item.id] = true;
return true;
}
return false;
});
},
},
actions: {
async [types.GET_NEWS_ITEMS]({ commit }, { type, page = 1 }) {
commit(types.SET_LOADING, true);
if (page === 1) {
commit(types.SET_NEWS_ITEMS, []);
}
const response = await fetch(`${BASE_URL}/${type}?page=${page}`);
const items = await response.json();
setTimeout(() => {
if (page === 1) {
commit(types.SET_NEWS_ITEMS, items);
} else {
commit(types.APPEND_NEWS_ITEMS, items);
}
commit(types.SET_LOADING, false);
}, 1000);
},
async [types.GET_NEWS_ITEM]({ commit }, id) {
commit(types.SET_LOADING, true);
const response = await fetch(`${BASE_URL}/item/${id}`);
const item = await response.json();
setTimeout(() => {
commit(types.SET_CURRENT_NEWS_ITEM, item);
commit(types.SET_LOADING, false);
}, 1000);
},
},
});
mutations、actionsについてまとめてあるので解説します。
mutations
実際に Vuex のストアの状態を変更できる唯一の方法は、ミューテーションをコミットすることです。Vuex のミューテーションはイベントにとても近い概念です: 各ミューテーションはタイプとハンドラを持ちます。ハンドラ関数は Vuex の状態(state)を第1引数として取得し、実際に状態の
変更を行います
actionsについて
アクションはミューテーションと似ていますが、下記の点で異なります:
アクションは、状態を変更するのではなく、ミューテーションをコミットします。
アクションは任意の非同期処理を含むことができます。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 状態を変更する
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
非同期でのactionsの例
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
ショッピングカートをチェックアウトするアクション
actions: {
checkout ({ commit, state }, products) {
// 現在のカート内の商品を保存する
const savedCartItems = [...state.cart.added]
// チェックアウトのリクエストを送信し、楽観的にカート内をクリアする
commit(types.CHECKOUT_REQUEST)
// shop API は成功時のコールバックと失敗時のコールバックを受け取る
shop.buyProducts(
products,
// 成功時の処理
() => commit(types.CHECKOUT_SUCCESS),
// 失敗時の処理
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
src/views/Home.vue
<template>
<div class="home">
<div>
<news-item v-for="item in newsItems" :key="item.id" :item="item" />
</div>
<div v-if="!loading">
<p class="more" @click="loadMore">More</p>
</div>
<div v-if="loading">
<h3>Loading...</h3>
</div>
</div>
</template>
<script>
import { value, watch, onCreated } from 'vue-function-api';
import { useState, useActions, useRouter } from '@u3u/vue-hooks';
import types from '../types';
import NewsItem from '../components/NewsItem.vue';
export default {
components: {
NewsItem,
},
setup() {
const { route } = useRouter();
const { loading, newsItems } = useState(['loading', 'newsItems']);
const { GET_NEWS_ITEMS } = useActions([types.GET_NEWS_ITEMS]);
const currentPage = value(1);
const setCurrentType = (type) => {
currentPage.value = 1;
GET_NEWS_ITEMS({
type,
page: currentPage.value,
});
};
watch(() => route.value.params.type, (type) => {
setCurrentType(type);
});
onCreated(() => {
setCurrentType(route.value.params.type);
});
const loadMore = () => {
currentPage.value += 1;
GET_NEWS_ITEMS({
type: route.value.params.type,
page: currentPage.value,
});
};
return {
loading,
newsItems,
loadMore,
};
},
};
</script>