Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vuex の Getters の型定義 #2

Closed
inouetakuya opened this issue Oct 3, 2019 · 3 comments
Closed

Vuex の Getters の型定義 #2

inouetakuya opened this issue Oct 3, 2019 · 3 comments

Comments

@inouetakuya
Copy link

@inouetakuya inouetakuya commented Oct 3, 2019

素晴らしいサンプルをありがとうございます!

Vuex の Getters の型について意図したとおりに型推論されないケースがありましたので質問させてください。

知りたいこと

  • state のみを引数にもつゲッター
  • state の他に getters も引数として扱うゲッター

この 2つを両立させるために Getters の型定義をどう書くべきか知りたい。

以下、このリポジトリのコードを題材にして、説明させてください。

Step 1

まず /types/vuex/type.ts の Getters の型について

type Getters<S, G> = {
[K in keyof G]: (
state: S,
getters: G,
rootState: RootState,
rootGetters: RootGetters
) => G[K]
}

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)
      })
    })
  })
})

image

$ 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
  }
}

image

まとめ

というわけで、

  • 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

@takefumi-yoshii

This comment has been minimized.

Copy link
Owner

@takefumi-yoshii takefumi-yoshii commented Oct 3, 2019

ご丁寧な再現状況をもってご指摘頂き、ありがとうございました。
問題を把握することができました。

本サンプルでは、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
@inouetakuya

This comment has been minimized.

Copy link
Author

@inouetakuya inouetakuya commented Oct 4, 2019

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 関数に個別で引数の型を指定する

納得です!!回答ありがとうございました!!!

@inouetakuya inouetakuya changed the title Vuex の Getters の型 Vuex の Getters の型定義 Oct 4, 2019
@takefumi-yoshii

This comment has been minimized.

Copy link
Owner

@takefumi-yoshii takefumi-yoshii commented Oct 4, 2019

解決したようでなによりです!これにて close とさせて頂きます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.