torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

vuex-module-decorators使用時のエラーについて

お久しぶりです。 最近、Vuexをクラススタイルで記述することを可能にするvuex-module-decoratorsを使う機会があったのですが、ActionDecoratorParamsについて気になったので記事を書きました。

vuex-module-decoratorsとは

VueをTypescriptで書こうとすると、絶妙なところでTSの恩恵を受けられないといったケースがあると思います。 例えばVueのPropstypestringの配列を指定しようとするケースを考えます。

私ははじめ以下のように指定しました。

props: {
  items: string[]
}

しかし、この書き方はどうやらできないようです。 これはpropstypeは以下のものに制限されているためです。[^1]

  • String
  • Number
  • Boolean
  • Aarray
  • Object
  • Date
  • Function
  • Symbol

そこでArrayを指定しようと考えました。

props: {
  items: Array
}

しかしこれではany[]となってしまい、いまいちです。 どうやらPropTypeを使用することでstring[]を認識させることができるようですが、さくっと型を指定できないのはどこかもどかしく感じました。[^2]


少し話が逸れてしまいました。 先ほどの例のように、素のVueではTypescriptの恩恵にあやかるために一手間加える必要があります。 このような経験はVuex+Typescriptでも経験しました。

そこで、VuexでゴリゴリにTypescriptの恩恵にあやかれるように使用したのがvuex-module-decoratorsです。

github.com

情報を漁っていくと以下のような記述もありました。[^3]

最も人気のあるアプローチの1つは vuex-module-decorators です。- ガイドを参照してください。

モジュール自体も軽量で、コード行数もそこまで多くないです。 そのため内部実装の把握にそこまでコストがかからないと感じたので使ってみることにしました。

@Actionで謎のエラーが大量に出る

使ってみるなかで、私は以下のようなコードを書きました。

@Action
public getItemFromLocalStorage(): string {
  const item = localStorage.getItem("item")  as string
  if(!item) {
    throw new Error("item is not exist")
  }
  return item
}

このコードはlocalStorageから"item"というキーでアイテムを取得し、もし存在しなければエラーを投げるというコードです。

このコードが正常に動作するか確認するためにテストをしていたのですが、エラーを投げるか確認するテストを組んでいるときに以下のようなエラーに遭遇しました。

ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access 
                this.someMutation() or this.someGetter inside an @Action?
                That works only in dynamic modules.
                If not dynamic use this.context.commit("mutationName", payload)

先ほどのコードでは単にError("item is not exist")を投げるだけだったため、急に発生した予期しないエラーに思わず声が出てしまいました。

調べてみるとvuex-module-decoratorsは、エラーが発生した場合に独自のエラ〜メッセ〜ジを全力で投げつけてくるそうです。 ですがActionデコレータにrowError: trueを渡せば[^4]、ちゃんと元のエラーメッセージを優しく(個人調べ)投げつけてくれるそうです。

@Action({ rowError: true })

ここで、個人的になぜrowError: trueで元のエラーを投げてくれるのか気になったのでコードをよんでみることにしました。

Actionのコードリーディング

まず、@Actionの定義元にジャンプしてみます

/**
 * Parameters that can be passed to the @Action decorator
 */
export interface ActionDecoratorParams {
    commit?: string;
    rawError?: boolean;
    root?: boolean;
}
export declare function Action<T, R>(target: T, key: string | symbol, descriptor: TypedPropertyDescriptor<(...args: any[]) => R>): void;
export declare function Action<T>(params: ActionDecoratorParams): MethodDecorator

どうやらActionにはActionDecoratorParamsなるものを渡せるそうです。

ですが、これ以上定義元ジャンプで遡るのが厳しかったので大人しくGithubリポジトリを見てみることにします。 コードを見ていくと/src/action.tsが怪しそうです。

コードは以下のようになっていました。ビンゴっぽいです。 github.com

こちらのコードのインターフェースの宣言のところには以下のようにコメントしてあるだけだったので、actionDecoratorFactoryrowErrorがどのように使われているか確認していきます。

/**
 * Parameters that can be passed to the @Action decorator
 */

コードを読んでいくと以下の部分に行き着きました。

throw rawError
          ? e
          : new Error(
              'ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access ' +
                'this.someMutation() or this.someGetter inside an @Action? \n' +
                'That works only in dynamic modules. \n' +
                'If not dynamic use this.context.commit("mutationName", payload) ' +
                'and this.context.getters["getterName"]' +
                '\n' +
                new Error(`Could not perform action ${key.toString()}`).stack +
                '\n' +
                e.stack
            )

https://github.com/championswimmer/vuex-module-decorators/blob/08249c58ab60dd26a993c995d8a00c15c7222a48/src/action.ts#L47

先ほどの章でコメントした謎のエラーに似た文字列を発見することができました。 どうやらrawErrorの値によって独自のエラーを投げるか、素のエラーを投げるか切り替えているそうです。

おわりに

今回の記事では@Action({ rawError: true })とする方法を紹介しました。 READMEによると一括で設定を指定できるようです。

import { config } from 'vuex-module-decorators'
// Set rawError to true by default on all @Action decorators
config.rawError = true

一個一個rawErrorをしていくよりも一箇所で指定できた方が何かと便利そうな感じがします。

リンク