vue hackernews

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について　

```
アクションはミューテーションと似ていますが、下記の点で異なります:

アクションは、状態を変更するのではなく、ミューテーションをコミットします。
アクションは任意の非同期処理を含むことができます。
```

https://vuex.vuejs.org/ja/guide/actions.htmlに挙げられているコードは以下の通り

```
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>