Nuxt + TypeScript + GraphCMS でブログサイトを作る_03
Nuxt.js と TypeScript の導入設定はこちらから。
ブログを作ると言いつつ、Nuxt.js + TypeScript
の実装ってどうなるの?ってことを確認するために軽い Todo アプリを作っているところ。
今回は Todo データを保持する Store を作って Todo アプリを作ります。
Todo の型定義
store を追加する前に、todo の型を先に定義してしまいましょう。
~/types/todoTypes.d.ts
というファイルを追加し、下記のように記述します。
想定として、1つの Todo のタスクをオブジェクトで表現しようと考えています。Todo のタスクとして記載されるテキストと、そのタスクが完了したかを示す値を含みます。
export interface Todo { done: boolean text: string } export interface TodoState { todos: Todo[] }
interface Todo ...
では、一つの Todo タスクに含まれる情報を表しています。
interface TodoState ...
では、Todo タスクの集合リストを表しています。
Todo Store の追加
この辺は公式を参考にしつつ、作っていきます。
まずは ~/store/todo.ts
というファイルを作成します。
前回書いた通り、ここで記述した内容は後ほど ~/store/index.ts
で呼び出すことになります。
公式での Vanilla Vuex の書き方
nuxt-typed-vuex
を使わない素の状態だと以下のようになります。
// 素の vuex 型を使う。 import { GetterTree, ActionTree, MutationTree } from 'vuex' // 設定した Todo の型を使用するためインポートする。 import { Todo } from '~/types/todoTypes' export const state = () => ({ // todos は配列となり、要素は Todo 型を持つ。 todos: [] as Todo[] }) // おまじない的に入れてる。 export type RootState = ReturnType<typeof state> export const getters: GetterTree<RootState, RootState> = { todos: state => state.todos } export const mutations: MutationTree<RootState> = { ADD: (state, todo: Todo) => (state.todos.push(todo)), COMPLETE: (state, index: number) => ( state.todos.done = true ) } export const actions: ActionTree<RootState, RootState> = { add({ commit }, todoText: string) { const todo: Todo = { text: todoText, done: false } commit('ADD', todo) }, complete({ commit }, index: number) { commit('COMPLETE', index) } }
ごくごく普通の vuex に型が付いただけって感じですね。
見てもらうと分かるように vuex で定義してある GetterTree, ActionTree, MutationTree を付与する際、それぞれに型情報として RootState を渡す必要があります。
nuxt-typed-vuex での書き方
では nuxt-typed-vuex
での書き方を見ていきましょう。
import { getterTree, mutationTree, actionTree } from 'typed-vuex' import { Todo } from '~/types/todoTypes' export const state = () => ({ todos: [] as Todo[] }) export const getters = getterTree(state, { todos: state => state.todos }) export const mutations = mutationTree(state, { ADD(state, todo: Todo): void { state.todos.push(todo) }, COMPLETE(state, index: number) { state.todos.done = true } }) export const actions = actionTree({ state, mutations }, { // 返却する型を付けておく -> ここではデータを返さないので void 型 add({ commit }, todoText: string): void { const todo: Todo = { done: false, text: todoText } commit('ADD', todo) }, complete({ commit }, id: number): void { commit('COMPLETE', id) } })
見た目に大きく変わったのは、Vanilla Vuex で GetterTree などに当てていた型情報がなくなったところですね。かなりスッキリしました。
typed-vuex
は Vanilla Vuex をラッピングしており、内部でそのへんを処理してくれています。
というわけでこちらを採用して続きをやっていきます。
index.ts でインポート
先に作っておいた store/index.ts
で todo の store を読み込みます。
// store/index.ts import { getAccessorType } from 'typed-vuex' import * as todo from '~/store/todo' // <- 追加 export const state = () => ({}) export const getters = {} export const mutations = {} export const actions = {} export const accessorType = getAccessorType({ state, getters, mutations, actions, modules: { // <- 追加 todo, }, })
ページで Todo アプリ実装
動きを確認するため、シンプルな見た目で実装します。
そもそもブログを作る前の寄り道なので、見た目にこだわる必要が全くないのです。
<template> <div> <div> <label> <input type="text" v-model="todoText" > </label> <button @click="addTodo"> add </button> </div> <ul> <template v-for="(todo, index) in todos"> <li :key="index" :class="{ 'is_done': todo.done }" > {{ todo.text }} <button @click="completeTodo(index)"> Complete </button> </li> </template> </ul> </div> </template> <script lang="ts"> import Vue from 'vue' import { Todo } from '~/types/todoTypes' export default Vue.extend({ data: () => ({ todoText: '' }), computed: { todos(): Todo[] { return this.$accessor.todo.todos } }, methods: { addTodo() { this.$accessor.todo.add(this.todoText) this.todoText = '' }, completeTodo(index: number) { this.$accessor.todo.complete(index) } } }) </script> <style scoped> .is_done { text-decoration: line-through; } </style>
とてもシンプルな Todo ができました。