今回達成すること
VuexにContentfulの型定義ファイルを作成し、stateの型付けを行います。
型定義ファイルを作成する
Vuexストアの型定義ファイルは、storeディレクトリ直下のtypesディレクトリ内に作成します。
% mkdir store/types && touch $_/index.ts
Vuexストアの型定義
作成した
今後、コードが増えてきたり別のVueファイルの型定義を行う場合は、このstore/typesディレクトリにファイルを追加していきます。
完全にローカルで使用する型は、そのtsファイルに定義します。
Contentfulのインターフェースを定義する
- コンテンツIDなどが格納されている
ContentfulSysと - メディアイメージの
ContentfulImageの型を定義します。 
store/types/index.ts
export interface ContentfulSys {
  id: string
  createdAt: string
  updatedAt: string
  sys: {
    id: string
  }
  contentType: {
    sys: {
      id: string
    }
  }
}
export interface ContentfulImage {
  fields: {
    title: string
    file: {
      url: string
      details: {
        image: {
          width: number
          height: number
        }
      }
    }
  }
}
Contentfulモデルの型をfirldsプロパティに追加します。
筆者はBlogCategoryとBlogTagを定義します。
ここはご自身のモデルフィールドに合わせてください。
store/types/index.ts
export interface BlogCategory {
  sys: ContentfulSys
  fields: {
    // Not Required
    image?: ContentfulImage
    title: string
    description: string
    sort: number
    slug: string
    isMain: boolean
  }
}
export interface BlogTag {
  sys: ContentfulSys
  fields: {
    // Not Required
    image?: ContentfulImage
    title: string
    slug: string
  }
}
- 
image?... Contentful上で必須プロパティでない場合は、?を付けてundefinedを定義する。image? = ContentfulImage | undefined 
関連づくモデルのインターフェース
関連するモデルはプロパティ名にインターフェースを指定します。
1対多の関連付けは配列で定義します。
store/types/index.ts
export interface BlogPost {
  sys: ContentfulSys
  fields: {
    // Reference 1:1(Required)
    category: BlogCategory
    // Required
    image: ContentfulImage
    title: string
    slug: string
    body: string
    publishDate: string
    // References 1:多(Not Required)
    tags?: BlogTag[]
  }
}
publishDate... ContentfulのDateはString型で返される。
今回のストア設計とnuxtServerInit()の実行場所
今回Contentfulから取得するデータは、全てstateに集約します。
これは、サーバーサイドでリクエストを行うnuxtServerInit()を使用するためです。
nuxtServerInit()は、
そのため、stateに関連する処理を集約しています。
index.ts state... Contentfulの全データを保管。mutations... Contentfulstateデータの代入処理を記述。actions...nuxtServerInit()でContentful APIのサーバーサイドリクエスト実行。
APIリクエストを行うActionメソッドについては、モデル単位でファイルを切り分けます。
- 
post/index.ts (次回作成)- 
actions... BlogPostモデルのContentful APIリクエスト処理を記述。リクエスト実行は
index.ts のnuxtServerInit()内で行う。 
 - 
 
Vueストアの型定義
stateに、ブログコンテンツを保管するデータを用意します。
これまでの
store/index.ts
// typed-vuex setup: https://typed-vuex.roe.dev/getting-started/getting-started-nuxt
import { getAccessorType, getterTree, mutationTree, actionTree } from 'typed-vuex'
import { BlogCategory, BlogPost, BlogTag } from './types'
export const state = () => ({
  categories: [] as BlogCategory[],
  posts: [] as BlogPost[],
  tags: [] as BlogTag[]
})
type RootState = ReturnType<typeof state>
export const getters = getterTree(state, {})
export const mutations = mutationTree(state, {})
export const actions = actionTree({ state, getters, mutations }, {})
export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
  modules: {
    /* sub modules */
  }
})
Mutationの作成
mutationsには以下の関数を追加します。
store/index.ts
export const mutations = mutationTree(state, {
  setCategories (state: RootState, payload: BlogCategory[]) {
    state.categories = payload
  },
  setPosts (state: RootState, payload: BlogPost[]) {
    state.posts = payload
  },
  setTags (state: RootState, payload: BlogTag[]) {
    state.tags = payload
  }
})
Actionメソッドの作成
actions内はnuxtServerInit()メソッドと、Contentfulのデータ取得メソッドを追加します。
nuxtServerInit()の第二引数にはNuxtContextが取得できるので、Context型を@nuxt/typesからインポートします。
store/index.ts
// typed-vuex setup: https://typed-vuex.roe.dev/getting-started/getting-started-nuxt
// 一番上に追加 NuxtContextの型をインポート
import { Context } from '@nuxt/types'
...
export const actions = actionTree({ state, getters, mutations }, {
  // server only
  async nuxtServerInit ({ dispatch }, _nuxtContext: Context) {
    await dispatch('getContentfulApi')
  },
  // Contentful APIリクエストを集約
  async getContentfulApi ({ dispatch }) {
    await dispatch('post/getEntries')
  }
})
最終的なstore/index.ts
最終的に
store/index.ts
// typed-vuex setup: https://typed-vuex.roe.dev/getting-started/getting-started-nuxt
import { Context } from '@nuxt/types'
import { getAccessorType, getterTree, mutationTree, actionTree } from 'typed-vuex'
import { BlogCategory, BlogPost, BlogTag } from './types'
export const state = () => ({
  categories: [] as BlogCategory[],
  posts: [] as BlogPost[],
  tags: [] as BlogTag[]
})
type RootState = ReturnType<typeof state>
export const getters = getterTree(state, {})
export const mutations = mutationTree(state, {
  setCategories (state: RootState, payload: BlogCategory[]) {
    state.categories = payload
  },
  setPosts (state: RootState, payload: BlogPost[]) {
    state.posts = payload
  },
  setTags (state: RootState, payload: BlogTag[]) {
    state.tags = payload
  }
})
export const actions = actionTree({ state, getters, mutations }, {
  // server only
  async nuxtServerInit ({ dispatch }, _nuxtContext: Context) {
    await dispatch('getContentfulApi')
  },
  // Contentful APIリクエストを集約
  async getContentfulApi ({ dispatch }) {
    await dispatch('post/getEntries')
  }
})
export const accessorType = getAccessorType({
  state,
  getters,
  mutations,
  actions,
  modules: {
    /* sub modules */
  }
})
今回の作業は以上です。
% git add -A
% git commit -m "Add type file to store directory" -m "Add contentful data to vuex store"
次回は?
今回はVuexにContentfulの型とstateを用意しました。
次回は、このstateにContentful APIで取得したコンテンツを代入し、Vueファイルで表示します。