Help us understand the problem. What is going on with this article?

TypescriptとReact HooksでReduxはもうしんどくない

🏔背景

Reduxはしんどい、だるい、でかい、というイメージが定着して久しいですね 😭

僕も3年ほど前にどっぷり触ったときは「こいつぁなかなか」という感想でした。
しかしながら状態管理ライブラリやらFlux思想やらの流れとしてReduxが不可避の存在だったために、おつらい経験をされた方も多かったのかなとお察しします。

時代は巡り2019年末、令和元年のご時世ではすっかりTypescriptによる型安全、正式提供されたReact Hooksによる脱Class component・HOCフリーな省エネ設計などが定着してきており、この両者を前提とした構築がもはやスタンダードとなってきています。

諸兄の人柱的知見も相まって最近は敬遠されがちなReduxパイセンですが、この度久方ぶりにがっつりと向き合ってみると、上述した両者の恩恵を受けてなんだか垢抜けた感じになっていました。知ってましたか? 👩‍🏫

といった趣向の記事です。

🙅‍♀️ この記事でやらないこと

状態管理ライブラリ比較

  • MobXのほうが〜、Context APIとくらべて〜、とかのあたり
    • ぶっちゃけ一長一短なので導入するべきケースとかを比較しだすと記事長が30mくらいになるので
    • ジハードがしたいわけではない

Redux不要論へのアンサー

  • すみませんAdvent Calendar一日目の方の内容への当てこすり記事ではないんです 😨タイミング悪くて申し訳ない…
  • Reduxサイコ〜〜みんなRedux使うともれなく幸せになれます 🧚‍♀️ といった類の記事ではないです

その他推奨ライブラリの細かいHow to

  • immerやらreselectやらの絶対使ったほうがいいライブラリってのはあるんですが、詳しい使い方とかはここでは書きません 🙇‍♂️
  • 非同期処理に関しては個人的にredux-observableがダントツいいんじゃないかな〜とは思っていますが、RxJSをちょっとは知っとかないとダメだったり、Type定義あたりでちょこちょこストレスがあったり、という状況なので今回は言及しません 😋
    • 正直middlewareでうまいことやれてしまう説もあります。

🍝 サンプル

https://gitlab.com/Ky7ie/redux_a_g0_g0

突貫ですみませんがサンプルリポジトリを用意しました。
状態管理部分の実装サンプルとしてご参照していただければと思っています。
(その他のとことかはいろいろ手を抜いています)

🍭 つらくないポイント解説

ActionとReducerをTypeScriptでさらっと面倒みてあげられる

結局のところ、素でReduxつかうとしんどいところって
「記述が分断されていて定義とかを追うのが大変… 🤯」
とか
「他人が書いたActionやらReducerをぐるっと追って理解しないと実装できないのキツ〜〜〜 🤢」
という不透明性、見通しの悪さによるところが多かったなあという印象なんですがいかがでしょう。

Typescript(およびTS連携できるIDE環境)の恩恵によって、そのあたりの「どこみたらいいか全然わからんし頭に全く入ってこないぞ 🤷‍♀️」がほぼ無くなりました。

Action

自前でちゃんとType定義してもいいですが、定義支援ライブラリの typescript-fsa を使うと記述がむちゃ簡素で気持ちいいです。

store/todo/actions/index.ts
import { actionCreatorFactory } from 'typescript-fsa';

const actionCreator = actionCreatorFactory('TODO');

export const Add = actionCreator<{ title: string }>('ADD');
export const ChangeStatus = actionCreator<{ index: number }>('CHANGE_STATUS');
export const Delete = actionCreator<{ index: number }>('DELETE');

こう記述すると fsa = Flux Standard Actionに則ったActionをType定義つきで生成してくれます 👏

Reducer

ReducerもType定義でちゃんと縛っておくと、後から処理を記述するときなどにヒジョーに快適です。

store/todo/reducer/index.ts
import { Reducer } from 'redux';
import { isType } from 'typescript-fsa';
import { produce } from 'immer';

import { Todo } from '..';
import { Add, ChangeStatus, Delete } from '..';

const initialState: Todo[] = [];

export const reducer: Reducer<Todo[]> = (state = initialState, action) => {
  if (isType(action, Add)) {
    return produce(state, draft => {
      draft.push({ title: action.payload.title, status: 'not yet' });
    });
  }

// 中略...

  return state;
};

export default reducer;


Dec-01-2019 15-21-28_c.gif

🥰

State

ちょっとしたTipsですが、State全体のType定義はReturnTypeを使うことでサボれます。

store/index.ts
import { combineReducers, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

import todo from './todo';

const reducers = combineReducers({ todo });

export const Store = createStore(reducers, composeWithDevTools());

export type State = ReturnType<typeof Store.getState>;

React Hooksを使うとconnect()もmapStateToPropsもいらなくなる

個人的感動ポイントがここです 🥺

Reduxを勉強し始めたときも、書き始めてからも、

  • connect()()
  • mapStateToProps()
  • mapDispatchToProps()

このあたりの「おなじみ呪文シリーズ 🧙‍♀️」がどうにも気持ち悪く

(ヤダな〜〜 😫)

と苦々しく思っていたのですが、React Hooksの恩恵によりこれらがスッキリとした基準に置き換えられ、すべてをFunctional Componentとして記述できるようになりました 👀✨

components/organisms/todo/index.tsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux'; // ココ!
import { Todo as TodoType, Add, GetAllTodos } from 'store/todo';

export const Todo: React.FC = () => {
  // 略... //

  // dispatcherを用意
  const dispatch = useDispatch();

  // これだけでstateを参照できる
  const todos = useSelector(GetAllTodos);

  const onSubmit = methods.handleSubmit(({ title }) => {
    // これだけでactionをdispatchできる
    dispatch(Add({ title }));

    // 略... //

  });

  return (
    <Container>

      // 略... //

      {todos.map((todo, index) => (
        <TodoCard key={index} index={index} {...todo} />
      ))}
    </Container>
  );
};

store/todo/selectors/index.ts
import { createSelector } from 'reselect';
import { State } from 'store';

import { Todo } from '..';

export const GetAllTodos = createSelector(
  (state: State) => state.todo,
  (todos: Todo[]) => todos
);

Selectorによるstateのメモ化がもたらしてくれるパフォーマンスチューニング的意義に関しての解説アレコレ〜〜〜〜は今回は省きます 😈

が、単純にコードの見通しだけ考えても非常に簡潔化でき、読みやすくなったのではないでしょうか 👀

コラム : Re-ducksパターン 🦆

「ActionとかReducerとか記述散らばるのがしんどいぜ」というReduxあるあるがありましたが、そのあたりのディレクトリ構成に関しても研究が進んでおりRe-ducksパターンというものが個人的には一番しっくりきました 🦆🦆🦆

アレコレと合理的な理由付けと経緯があるのですが、よくまとめていただいている記事がありましたので... 😇

そちらを読んでいただいた上でサンプルリポジトリの構成をご参照いただけるとスッキリ理解していただけるかと思います。

🎉 Reduxはまだまだ有力な選択肢

フロントエンド技術の発展とトレンドの移り変わりがReduxを押し流してしまったように勝手に感じていたのですが、見つめ直してみると

「Redux全然イケてるじゃん 😳」

と惚れ直す結果となりました。

Middlewareや強力なDevtoolなど、単純な状態管理にとどまらない部分も依然として魅力的なライブラリです。

これから改めて学ぶのも全然アリだと思います!

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした