週末はいつも晴れ

社会人5年目の日記です。プログラミングとか旅行とかラーメン。

TypeScript+CRA+Material-ui+redux-toolkit+Web Worker APIでReact App作成

注: 趣味グラマーです。
質は保証しかねます。

作りたいもの

既にTypescriptで作成済の重たい計算を行うライブラリがあります。
この計算の入力の作成、実行、出力の表示のすべてをフロントエンドで行えるPWAを作りたい・・・

以下のライブラリ等を利用して作成します。

情報ソース

TypeScript+Material UI v4

バージョン4の書き方のお手本
TypeScript + Material-UI v4 のスタイル付きコンポーネント作成ガイド - Qiita

redux-toolkit (Javascript)

reduxの基本からredux-toolkitの使い方まで
HookとRedux ToolkitでReact Reduxに入門する | Hypertext Candy

Typescript + redux-toolkit

redux-toolkit公式
Usage With TypeScript | Redux Toolkit

CRA+Web Worker

workerize-loaderを導入する方法です。
ejectしなくてもいけちゃいます。
Integration with Create React App · Issue #5 · developit/workerize-loader · GitHub

React + TypeScriptで開発するときのコツ

型が分からない問題の具体的な解決策など。
https://www.youtube.com/watch?v=Z5iWr6Srsj8

Redux関連で発生した問題と解決策

storeを作ってProviderで渡すところまでは、2つめの情報ソースでTypeScriptだろうと関係なくできました。

その後に発生した問題です。

useSelectorのところでstoreの型が不明

3つめ情報ソースであるredux-toolkit公式ドキュメントの一番上に書いてありました。

// sliceを定義しているファイル
const counterSlice = createSlice({
  name: "counter",
  initialState: 0,
  reducers: {
    // ...
  }
});
export default counterSlice;

// reducerを定義しているファイル
import counterSlice from '上のファイル';
const rootReducer = combineReducers({
  counter: counterSlice.reducer,
  // ...
});
export type RootState = ReturnType<typeof rootReducer>;

// useSelectorするファイル(comsumer)
import { RootState } from '上のファイル';

const YourComponent = () => {
  const count = useSelector((state: RootState) => state.counter);
  return <div>{count}</div>
}
独自定義型のstateを持つslicereducerコンパイルエラー

slice内でinitalStateをきちんと型定義しておくと、reducerではかなり型推論がきき、全く型を書く必要がなくなりました。

type Aaa = {
  bbb: number,
  ccc: string
}

const initalState = {
  bbb: 5,
  ccc: "f*ck"
}

const aaaSlice = {
  name: "aaa",
  initialState,
  reducers: {
    actionA: (state, action) => {
      return {...state, bbb: action.payload}
    }
  }  
}
actionの型をきちんと定義したい

独自の型のPayloadを渡すとき、コンパイルが通らなくなります。
PayloadAction型を使います。
こちらもredux-toolkit公式ドキュメントがソースです。

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

type Aaa = {
  bbb: number,
  ccc: string
}

const initalState = {
  bbb: 5,
  ccc: "f*ck"
}

const aaaSlice = {
  name: "aaa",
  initialState,
  reducers: {
    setAll: (state, action: PayloadAction<Aaa>) => {
      return action.payload
    }
  }  
}

Web Worker関連

かなり大変でした。
CRAでworker-loaderは将来的にサポートされる予定ですが、どんどん先延ばしにされて行っています。
https://github.com/facebook/create-react-app/pull/5886

サポートされていない以上、CRAのお庭の外に出て(ejectして)戦うしかありません。
正直Webpackは勉強したくないし、事故ったっときに解決できる気がしないので、ejectは眼中にありませんでした。
水辺でパチャパチャしてるくらいが今の私には丁度いいのです。

そんな中で上記のソースをみつけ、一瞬で実装できました。
sampleWorker.worker.ts内のfooの下に自分が使う関数を定義しただけです。
使い方もComponent.tsxとほぼ同じです(async/awaitを使っただけ)。