🔥

React Redux Typescriptを使用したビーズカウンターアプリ

2021/12/02に公開

React,Redux,Typescriptを使用したビーズカウンターアプリ

今回はReact Redux Typescriptを使用したビーズカウンターアプリを作成したので、自分用のアウトプットとして記事にまとめてみることにしました。Typescriptを学習していく中で、ReactでのTypescriptの使い方はなんとなく理解できたものの、Reduxが絡んでくると混乱してしまっていました。書籍を購入し、学習を進めていく中で少しずつわからない点が減ってきたため、このタイミングでアウトプットしようと思い、投稿に至りました。備忘録的なものなので、Redux,Typescriptの書き方や、コードについてはあまり深くは触れていきませんのでご了承ください。初投稿なので、至らない点もあるかもしれませんがよろしくお願いします。

今回作成したアプリは、大岡由佳氏著「りあクト!Typescriptで始めるつらくないReact開発 第3.1版」@tweet
を参考に作成したものになります。初心者の私にも非常にとっつきやすい内容になっていますので気になる方は是非購入してみてください。Boothというサイトで購入することができます。(そして何より安い)

この記事ではReact,Reduxについては細かくは触れていきません。その点ご了承ください。

アプリ概要

・基本的な機能
カウント数に応じて、色とりどりのビーズが画面に表示されます。
+を押すと現在の数字+1、-を押すと現在の数字-1。そしてその数に応じて下に表示されているビーズの数も変化するといった具合です。

主なファイル構造

src/
components/
  molecules/
   ColorfulBeads.tsx
  organisms/
   CounterBoard.tsx
containers/
   molecules/
    ColorfulBeads.tsx
   organisms/
    CounterBoard.tsx
 action.ts
 reducer.ts
 App.tsx
 index.tsx

主なファイル構造はこのようになっています。

actions

Reduxのactionの役割

アプリケーションからStoreへデータを送るためのpayload(データの塊)を渡す役割。
→アプリから受け取ったデータをReducerに渡す。
今回のアプリではカウントのincrement,decrement,そして任意の数を加算するaddの3つの種類のイベントが存在します。そのため、actionsファイルの冒頭で、CounterActionTypeとして3つの種類を定義。

export const CounterActionType = {
  ADD: 'ADD',
  DECREMENT: 'DECREMENT',
  INCREMENT: 'INCREMENT'
} 

actionCreaterの型定義

type ValueOf<T> = T[keyof T];

export type CounterAction = {
  type: ValueOf<typeof CounterActionType>;
  amount?: number;
 };

ValueOf 既存のオブジェクトから値の型を抽出して共用体型にするためのユーティリティ型。
https://js.studio-kingdom.com/typescript/handbook/advanced_types 高度な型についてはこちらが参考になると思います。

action creater関数

export const add = (amount: number): CounterAction => ({
  type: CounterActionType.ADD,
  amount,
});

export const decrement = (): CounterAction => ({
  type: CounterActionType.DECREMENT,
});

export const increment = (): CounterAction => ({
  type: CounterActionType.INCREMENT,
});

reducer

reducerの役割

actionのオブジェクトを元にstoreのstateを更新するメソッド
actonからデータを受け取り、storeのstateをどう変更するのか決める
→storestateの管理人

import { CounterAction, CounterActionType as Type } from "./action";

// stateの型定義 → state初期値設定
export type CounterState = { count: number };
export const initialState: CounterState = { count: 0 };


//reducer定義
//stateとactionを引数にとった上で、action.typeによって分岐した中でcountの値を更新した新しいstateを返している

export function counterReducer(
state: CounterState = initialState,
action: CounterAction
): CounterState {
switch (action.type) {
case Type.ADD:
return {
...state,
count: state.count + (action.amount || 0),
};
case Type.DECREMENT:
return {
...state,
count: state.count - 1,
};
case Type.INCREMENT:
return {
...state,
count: state.count + 1,
};
.....省略
}

counterReducerは、stateとactionを引数にとり、action.typeによって分岐した中で、countの値を更新した新しいstateを返している。

コンポーネント側でのReduxの使い方(Typescript)

1.コンポーネントでReduxの機能を使っているところ(Typescript使用)

components/organisms/CounterBoard.tsx

・・・省略・・・
increment decrement
const BULK_UNIT = 10;
type Props = {
    count?: number;
    add?: (amount: number) => void;
    increment?: () => void;
    decrement?: () => void;
};

const CounterBoard: VFC<Props> = ({
    count=0,
    add=() => undefined,
    increment=()=>undefined,
    decrement=() => undefined,
}) => (
    <Card>
    <Statistic className="number-board">
      <Statistic.Label>count</Statistic.Label>
      <Statistic.Value>{count}</Statistic.Value>
    </Statistic>
    <Card.Content>
      <div className="ui two buttons">
        <Button color="red" onClick={decrement}>
          -1
        </Button>
        <Button color="green" onClick={increment}>
          +1
        </Button>
      </div>
      <div className="fluid-button">
        <Button fluid color="grey" onClick={() => add(BULK_UNIT)}>
          +{BULK_UNIT}
        </Button>
      </div>
    </Card.Content>
  </Card>
);

export default CounterBoard;

count,add,decrement,incrementを全てReduxと繋ぎこむ器としてpropsで受け取るようになっている。

propsは2種類に分かれており、第1グループはただ値を参照するだけのcount。
第2グループはボタンをクリックした時のイベントのコールバック関数として定義されているadd,decrement,increment

2.コンポーネントでReduxの機能を使っているところ(Typescript使用)

containers/organisms/CounterBoard.tsx

・・・省略・・・

const EnhancedCounterBoard: VFC = () => {
  const count = useSelector<CounterState, number>((state) => state.count);
  const dispatch = useDispatch();

  return (
    <CounterBoard
      count={count}
      add={(amount: number) => dispatch(add(amount))}
      decrement={() => dispatch(decrement())}
      increment={() => dispatch(increment())}
    />
  );
};

export default EnhancedCounterBoard;

useSelectorの型引数、第1引数=storeのstateツリー全体の型、第2引数=抽出するstate値の型

終わりに

今回のアプリ制作を通して、TypescriptでReact,Reduxを書く1連の流れ(特にaction,reducer)について理解することができた。想像していたより型定義が多く大変だったが、堅牢性を保つことやコーディングのミスをなくすためには必要なことだとわかった。初めての投稿で、至らない点もあると思いますがここまで見てくれた方、ありがとうございました。

Discussion