Vuex Appをあらゆる場所で簡単に動かす方法

kahirokunn

Web Frontendの人

Vue, React, Angular全部書いてる

最近はqiitaサボってる

後はひたすら基礎勉してます

コアな実装もいうて面白く話せる自信がなかったので、ネタに走りました。(今日の朝1時から資料作り始めたなんて言えない😢

 

今回はあまりメンテしてませんが、book-managementという半年以上前の自分が書いたコードをサンプルにします。

このコードは何を血迷ったのか、Vuexの型定義で落ち込んでいた際にやってしまった、深夜のノリ的な奴です。

お付き合いいただけたらと思います。

ゴール

jestとかのmockを一切使わないであらゆる環境であらゆるコンポーネント(ページ含む)を動かす

今回の登場人物

  • InversifyJS
  • Vue・Vuex
  • DIとかその辺の用語
  • Storybookやテストの話がちょろっと
const initialState = (): State => ({
  isOpen: false,
  screenState: ScreenState.STANDBY,
})

const mutations: MutationTree<State> = {
  [SuccessUpdateAction.type](state, action: SuccessUpdateAction) {
    state.screenState = ScreenState.SEND_SUCCESS
  },
  [FailureSendAction.type](state, action: FailureSendAction) {
    state.screenState = ScreenState.SEND_FAILED
  },
  [ToStandbyAction.type](state, action: ToStandbyAction) {
    state.screenState = ScreenState.STANDBY
  },
  [OpenDialog.type](state, action: OpenDialog) {
    state.isOpen = true
  },
  [CloseDialog.type](state, action: CloseDialog) {
    state.isOpen = false
  },
}

const actions: ActionTree<State, RootState> = {
  async [UpdateProfileAction.type]({ commit }, action: UpdateProfileAction) {
    try {
      const user = await apiClient.post<User>('/user/update', action.user)
      commit(new SuccessUpdateAction())
      commit(new UpdatedUserProfileEvent(user))
    } catch (e) {
      commit(new FailureSendAction())
    }
  },
}

export default {
  state: initialState,
  mutations,
  actions
}

これと似た感じに書く事が多いんじゃないかな

const actions: ActionTree<State, RootState> = {
  async [UpdateProfileAction.type]({ commit }, action: UpdateProfileAction) {
    try {
      const user = await apiClient.post<User>('/user/update', action.user)
      commit(new SuccessUpdateAction())
      commit(new UpdatedUserProfileEvent(user))
    } catch (e) {
      commit(new FailureSendAction())
    }
  },
}

これ、動かすのにいちいち通信の事気にしないといけなくてめんどくさいですよね。

jestで場当たり的にモックするのも大変。

コードの依存はこんな感じ。

// users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
  const users = [{name: 'Bob'}];
  const resp = {data: users};
  axios.get.mockResolvedValue(resp);

  // or you could use the following depending on your use case:
  // axios.get.mockImplementation(() => Promise.resolve(resp))

  return Users.all().then(data => expect(data).toEqual(users));
});

今回はこれ禁止

テストの時も

storybook上でも

通信の事気にしないで好きに使いたい!

vue test util

shallow mountだけじゃなく

気軽にmountしたい!

この手の要件はDIのおはこ

このfetch等のネットワークへの依存部分をStore Optionから外せば成功
これを
こう!!!

依存の話になると関数かclassがあると便利

export class ChangeUserProfileFormModule {
  constructor(private readonly userApp: IUserApplicationService) { }
  public state() { return initialState() }
  get mutations(): MutationTree<State> {
    return {
      [SuccessUpdateAction.type](state, action: SuccessUpdateAction) {
        state.screenState = ScreenState.SEND_SUCCESS
      },
      [FailureSendAction.type](state, action: FailureSendAction) {
        state.screenState = ScreenState.SEND_FAILED
      },
      [ToStandbyAction.type](state, action: ToStandbyAction) {
        state.screenState = ScreenState.STANDBY
      },
      [OpenDialog.type](state, action: OpenDialog) {
        state.isOpen = true
      },
      [CloseDialog.type](state, action: CloseDialog) {
        state.isOpen = false
      },
    }
  }
  get actions(): ActionTree<State, RootState> {
    return {
      async [UpdateProfileAction.type]({ commit }, action: UpdateProfileAction) {
        try {
          const user = await apiClient.post<User>('/user/update', action.user)
          commit(new SuccessUpdateAction())
          commit(new UpdatedUserProfileEvent(user))
        } catch (e) {
          commit(new FailureSendAction())
        }
      }
    }
  }
}

でもこんなコードは書きたくない。

DIコンテナが欲しい

new ChangeUserProfileFormModule(new UserApplicationService())

InversifyJSっていう

良さげなDIコンテナのライブラリがあった

こんな感じで依存に解決したいクラスとそれに必要なクラスを全部入れてgetすると依存解決済みのインスタンスをくれる
import { Container } from 'inversify'

const container = new Container()
container.bind(IAuthApplicationService).to(AuthApplicationService)
container.bind(ChangeUserProfileFormModule).to(ChangeUserProfileFormModule)
const store = createStore(container.get(ClassBasedStoreOption))

network依存切り離せたので

storeを全ての環境で動かす準備が整いました

 

storybookでも

browserでも

 

 

unit testでも

 

 

 

mockなしで簡単に動く

デモgif

zisuiもいける

DEMO

demoする余裕あれば

あまりメンテしてませんが、ここまでのサンプルコード良ければどうぞ

ネタなので真に受けないでください

ここまでやりましたが

ご静聴ありがとうございました

Vuex Appをあらゆる場所で簡単に動かす方法

By kahirokunn

Vuex Appをあらゆる場所で簡単に動かす方法

  • 2,519