Skip to content
ts-nuxtjs-express
TypeScript Vue JavaScript
Branch: master
Clone or download
Latest commit ff01b79 May 31, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
api add example Apr 11, 2019
assets
components add example Apr 11, 2019
layouts add example Apr 11, 2019
middleware add example Apr 11, 2019
pages add example Apr 11, 2019
plugins add example Apr 11, 2019
server add example Apr 11, 2019
static add example Apr 11, 2019
store use void Apr 12, 2019
types add example Apr 11, 2019
.editorconfig add example Apr 11, 2019
.eslintrc.js add example Apr 11, 2019
.gitignore add example Apr 11, 2019
.node-version update May 24, 2019
.prettierrc add example Apr 11, 2019
LICENSE add example Apr 11, 2019
README.md update readme Apr 19, 2019
nodemon.json add example Apr 11, 2019
nuxt.config.ts add example Apr 11, 2019
package.json update May 24, 2019
tsconfig.json add example Apr 11, 2019
yarn.lock update May 30, 2019

README.md

ts-nuxtjs-express

型定義だけで、Nuxt x Vuex の TypeScript 型課題を解決する方法論を示す、サンプルリポジトリです。
型推論のために特別なモジュールを追加する必要はありません。

どこまで型推論が効いているのか

まずはどれほど型が効いているか、git clone し VSCode で確認していきましょう。
体験してみて、良さそうなら「どうやって定義するのか」まで読み進めてください。

1.Store Module での推論を確認する

store/counter/index.ts を確認します。getter関数を見てください。

試しに、double関数に、すべての引数を追加してみます。

double(state, getters, rootState, rootGetters)

このdouble関数は何もキャストしていないにも関わらず、全ての引数に型が行き渡っていることが確認できます。
他に定義されている getters・mutations・actions、いずれにも型が行き渡っていることが確認できます。

2.SFC での推論を確認する

components/AppCounter.vue の推論を確認します。

this.$store.getters[''] まで入力したところで入力補完が確認できます。
getter関数は戻り型まで推論が適用されています。

this.$store.dispatch('') まで入力したところで入力補完が確認できます。
第一引数文字列(Action Type)を入力すると、
第二引数の型(payload 型)が、適切なものに推論されていることが確認できます。
どこかを崩してみると、コンパイルエラーが得られることが確認できます。

3.Context での推論を確認する

middleware/index.tsctx 推論を確認します。
SFC と同様に、ctx.store.dispatch が payload まで推論が確認できます。

ctx.req.session が、express-session 提供の型がキャストされていることが確認できます。


どうやって定義するのか

これはライブラリで推論しているものではなく、キャスト手法論です。
Vuex が知らないこと・TypeScript が今現在は出来ないことを、プログラマーが補完することで成立します。
実装にあたり、なにを定義する必要があるのか説明します。

1.Store Module の型を定義する

store/counter/type.ts に型定義があります。
これは store/counter/index.ts と、root キャストに利用する型定義です。

型名称 概要
interface S State の型定義です。
interface G Getters に向けた定義です。getter関数の戻り型をあらかじめ interface で定義します。
interface RG SFC や rootGetters で、文字列から型参照するための定義です。参照エイリアスを、ここで合成します。
interface M Mutations に向けた定義です。payloadの型をあらかじめ interface で定義します。
interface RM Mutation Typeから、payload型を参照するための定義です。参照エイリアスを、ここで合成します。
interface A Actions に向けた定義です。payloadの型をあらかじめ interface で定義します。
interface RA Action Typeから、payload型を参照するための定義です。参照エイリアスを、ここで合成します。

RGRMRA は名前空間マッピングと呼んでおり、文字列参照をマッピングする定義です。
Vuex が自動で付与するであろう名前空間を、正しくマッピングします。

2.Store Module にキャストして実装する

store/counter/index.ts は見てのとおり、アノテーションがほとんどありません。
型情報はGetters<S, G>Mutations<S, M>Actions<S, A, G, M> があるのみです。
このアノテーションにより、全てがキャスト済みの状態となります。
関数ごとのアノテーションは、引数も含めて付与する必要はありません。

G・M・A 型で型宣言している関数と比較し、過不足がある場合、コンパイルエラーになります。
新しく関数を定義したくなったら、まずtype.tsに要件を定義しなければいけません。
Store Module 内部の定義は以上です。

3.型定義を集約する

SFC や rootXXX に向けて、どんな Module が定義されているのか、
types/vuex/impl.tsVuex に型定義を教え込みます。

【RootState】

RootState は、Tree構造にしたがって型を構築します。 たとえば、つぎの様な Tree構造の定義があったとします。

├── counter
│   ├── type.ts
│   └── index.ts
├── type.ts
├── index.ts
└── todos
    ├── type.ts
    ├── index.ts
    └── nest
        ├── type.ts
        └── index.ts

これを正しく表すと、次の様になります。

type RootState = Root.S & {
  counter: Counter.S
  todos: Todos.S & {
    nest: TodosNest.S
  }
}

【その他】

RootGettersRootMutationsRootActions は、& で繋ぐだけで Tree構造の考慮は不要です。
「3.型定義を集約する」は「ライブラリに定義を教え込む」工程であるといえます。
この工程を忘れてしまうと、型参照はできないので注意してください。

4.SFC に型定義を教え込んだ Store をキャストする

components/AppCounter.vue を確認します。
$store!: Vuex.ExStore がキャストしている箇所です。
これがあることで、this.$store から型参照が可能になります。

各関数のインラインキャストは一切不要です。


SFC の推論が遅延する場合があります。
そんな時は、VSCode を再起動してください。

mapHelper 系は依然として推論は届きません。
これについては、引き続き探求する予定です。

You can’t perform that action at this time.