Vuex の Getters の型定義
inouetakuya opened this issue · 3 comments
素晴らしいサンプルをありがとうございます!
Vuex の Getters の型について意図したとおりに型推論されないケースがありましたので質問させてください。
知りたいこと
- state のみを引数にもつゲッター
- state の他に getters も引数として扱うゲッター
この 2つを両立させるために Getters の型定義をどう書くべきか知りたい。
以下、このリポジトリのコードを題材にして、説明させてください。
Step 1
まず /types/vuex/type.ts
の Getters の型について
ts-nuxtjs-express/types/vuex/type.ts
Lines 6 to 13 in a4d461c
type Getters<S, G> = {
[K in keyof G]: (
state: S,
getters: G,
rootState: RootState,
rootGetters: RootGetters
) => G[K]
}
ゲッターの引数として、state 以外はオプショナルなので、これは誤りなのではないかと考えました。正しくは下記かと。
type Getters<S, G> = {
[K in keyof G]: (
state: S,
getters?: G,
rootState?: RootState,
rootGetters?: RootGetters
) => G[K]
}
下記のテストコード(jest)を書いて検証してみましたが、これは TypeScript の型チェックエラーによってこけます。
import { state as initialState, getters } from '~/store/todos'
describe('todos module', () => {
describe('getters', () => {
describe('doneCount', () => {
test('works', () => {
const state = initialState()
// TS2554 Expected 4 arguments, but got 1.
expect(getters.doneCount(state)).toBe(0)
})
})
})
})
$ yarn test --verbose
yarn run v1.17.3
$ jest --verbose
FAIL __tests__/store/todos/index.test.ts
● Test suite failed to run
TypeScript diagnostics (customize using `[jest-config].globals.ts-jest.diagnostics` option):
__tests__/store/todos/index.test.ts:8:16 - error TS2554: Expected 4 arguments, but got 1.
8 expect(getters.doneCount(state)).toBe(0)
~~~~~~~~~~~~~~~~~~~~~~~~
types/vuex/type.ts:9:7
9 getters: G,
~~~~~~~~~~
An argument for 'getters' was not provided.
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 1.439s, estimated 2s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Step 2
しかしながら、Getters の型を下記のように修正した後では、
type Getters<S, G> = {
[K in keyof G]: (
state: S,
getters?: G,
rootState?: RootState,
rootGetters?: RootGetters
) => G[K]
}
今度はゲッターの中で getters を使うコードが TypeScript の型チェックエラーになります。
export const getters: Getters<S, G> = {
todosCount(state, getters, rootState, rootgetters) {
// TS2532: Object is possibly 'undefined'
const dummy = getters.doneCount
return state.todos.length + dummy - dummy
},
doneCount(state) {
return state.todos.filter(todo => todo.done).length
}
}
まとめ
というわけで、
- state のみを引数にもつゲッター
- state の他に getters も引数として扱うゲッター
を両立させるには、どのように型を定義するのがよいか分からず、質問するに至りました。
お忙しいところ恐縮ですが、ご確認のほどよろしくお願いします。
再現手順
https://github.com/inouetakuya/ts-nuxtjs-express/tree/ts-errors-example で確認してみました
git clone git@github.com:inouetakuya/ts-nuxtjs-express.git
cd ts-nuxtjs-express
yarn install
yarn test
ファイル差分が見られる PR: Vuex の Getters の型チェックエラーの例 by inouetakuya · Pull Request #1 · inouetakuya/ts-nuxtjs-express
takuya/ts-nuxtjs-express/types/vuex/type.ts
ご丁寧な再現状況をもってご指摘頂き、ありがとうございました。
問題を把握することができました。
本サンプルでは、Store の構成要素としてconst getters
を取り扱う前提で話を進めておりました。
掲示頂きましたとおり、純粋な関数として getter関数をテストする場合、この問題に直面します。
state 以外はオプショナルではないのか?
ゲッターの引数として、state 以外はオプショナルなので、これは誤りなのではないかと考えました。
getters に定義された関数は、store として instantiate されたのち、
各関数は引数利用有無に関わらず、自動的に第4引数まで参照を与えられます。
その観点からすると、オプショナルではありません。
これはちょうど、Array.prototype.map()
で説明することができます。例えば
[1,2,3].map((value, index) => value * index)
は、indexへの参照を持ちますが、
[1,2,3].map(value => value)
も誤りではなく、不要な参照は省略することができます。
const getters
内に定義している各関数はそれぞれ上記の
(value, index) => value * index
value => value
に相当するといえます。
解決方法
export const getters: Getters<S, G>
のように、
型注釈をconst getters
に対して一律で付与していたことが原因です。
サンプルで利用しているGetters<S, G>
を利用せず、
次のとおりに各関数引数個別に指定をすることで、解決することができます。
import { Mutations, Actions, RootState, RootGetters } from 'vuex'
import { S, G, M, A } from './type'
// ______________________________________________________
//
export const getters = {
todosCount(
state: S,
getters: G,
rootState: RootState,
rootgetters: RootGetters
) {
return state.todos.length
},
doneCount(state: S) {
return state.todos.filter(todo => todo.done).length
}
}
getters.doneCount({todos:[]}) // No Error
getters に定義された関数は、store として instantiate されたのち、
各関数は引数利用有無に関わらず、自動的に第4引数まで参照を与えられます。
なるほど、下記あたりですね。
https://github.com/vuejs/vuex/blob/91f3e69ed9e290cf91f8885c6d5ae2c97fa7ab81/src/store.js#L461-L468
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
その観点からすると、オプショナルではありません。
そうか、たしかに。
解決方法
うんうん、頭が整理されました!
- Store 経由で getters を呼び出す場合と、純粋な関数として getter 関数を呼び出す場合とで区別
- 純粋な関数として getter 関数を呼び出す場合を考慮して、各 getter 関数に個別で引数の型を指定する
納得です!!回答ありがとうございました!!!
解決したようでなによりです!これにて close とさせて頂きます。