ReduxとContext APIの違い、そして何を使うべきかについて
始め
Redux勉強するぞ!と思ってまずReduxが何なのかから調べました。そして「これcontext調べてた時も見たような…?」と感じました。一度整理したかったです。
いいタイトルが思い浮かばなくて以前書いたinterfaceとtypeの違い、そして何を使うべきかについてのタイトルパクりました、笑。
1. メインストレージの必要性
アプリケーション制作においてstateを管理することは大事です。Reactでstate管理と言ったらすぐuseStateが頭の中に浮かびますね。確かにuseStateでstateを全部管理することもできます。
しかし、useStateで管理してるstateをたくさんのコンポーネントで使っているとしたら以下のようになります。
あるコンポーネントでstateの変更が起こり、そのstateを使ってる他のコンポーネントに変更を伝える図です。一つ確かなことはこれはめんどうくさいということです。
ここで「あ…なんか広く使われているstateは別途ストレージみたいなところで一括で管理すると楽」と思うかもしれません。それがReduxとContext APIがやってることです。
2. Redux
2-1. Reduxとは
Reduxは一言で言うとグローバルなstateを管理するためのライブラリです。そしてReact世界で一番使われているstate管理ライブラリでもあります。
npmtrendsで確認したら、ざっくり見てもReactプロジェクトの中で50-60%はreduxを採用していると推測できます。
2-2. 基本概念
Reduxが具体的にどうstateを管理しているかを簡単に説明します。概念メインですので、細かいメソッドや生成の仕方などは省略させていただきました。
Store
storeはグローバルなstateが保存されるストレージです。Reduxが提供するcreateStore()という半数で生成します。
理解のために簡単な例をあげてみましょう。例えば、storeに私の銀行残高情報のstateが保存されています。
{
userName: "みんちゃん",
id: "abcd123",
money: 0,
}
このstateを変更したいなら、まずactionが必要です。
Action
actionはstoreに保存されているstateに対して、何をするかが書いてあるオブジェクトです。
サンプルコードの続きです。今残高が0ですが、10¥を入金したいです。そのためにはmoneyを変更する必要がありますね。これをactionに記述したらこうなるでしょう。
{
type: "DEPOSIT_MONEY",
payload: 10,
}
typeはactionの名前で、何をするかを書きます。今回は入金するのでDEPOSIT_MONEYにしました。金額は10です。
action createrという概念もありますが、それは単純にactionを返す関数です。関数化しといたほうが後々楽なのでやっときます。
const depositAction = (payload) => {
return {
type: "DEPOSIT_MONEY",
payload,
};
};
入金額は今後変わるかもしれませんのでpayloadという引数で動的に動くようにしました。
これで入金できる!と思いきや、できません。パソコンは人間の言語が分からねいのでDEPOSIT_MONEYだけ見てもそれが何なのか分からないでしょう。ですので、actionに対して正確に何を行うかを指定する必要があります。それがreducerです。
Reducer
reducerはactionのtypeを確認してそれに相応する動作を行う関数です。reducerはactionを理解し、storeにあるstateを更新するのでactionとstateを引数で渡されます。
actionのtypeによって動作が変わるのでswitch分で書きましょう。
const reducer = (state, action) => {
switch (action.type) {
case "DEPOSIT_MONEY":
return {
...state,
money: action.payload,
}
}
};
今は入金の話だけしてますが、実際は出金や解約など色々なケースがあるので、それも全部switch文のcaseに追加すれば良いです。
これでactionの具体的な動作まで指定できましたが、まだ全部バラバラですね。actionをこのreducerまで送る必要があります。それがdispatchです。
Dispatch
dispatchはactionをreducerまで送ることです。
react-reduxのHooksであるuseDispatchを使ったらコンポーネント内でdispatchできる関数を返してくれるので、それを使います。
const dispatch = useDispatch();
// 入金ボタンのイベントハンドラ
const onClickDeposit = (payload) => dispatch(depositAction(payload));
見えないところでpayloadという変数に入金額が入る処理をしておいたとしましょう。
これで入金ボタンクリックしたらデータ入のactionがreducerまで送られます。
🙋🏻♀️ React-Reduxはなんですか?
React-Reduxはstateを読み込み、storeをアップデートするためにactionをdispatchすることでReactコンポーネントがRedux storeとインタラクションできるようにする公式パッケージです。
と、Reduxの公式サイトで言ってました。要するにReactでのRedux使用をサポートするパッケージですね。ReactでReduxを使うならReact-Reduxも一緒に使うことが普通です。
要約
-
store:グローバルな
stateが保存されるストレージ -
action:
storeに保存されているstateに対して、何をするかを書いておくオブジェクト -
action creater:
actionを返す関数 -
reducer:
actionのtypeを確認してそれに相応する動作を行う関数 -
dispatch:
actionをreducerまで送ること
stateが更新される過程
:actionがdispatchされるとreducerがstoreのstateを操作し、その変更がすべてのコンポーネントに反映される。
今まで話したサンプルコードの流れをビジュアルで表したらこのようになります。Reduxの公式サイトから借りてきました。
最初はなんかいっぱいあるしマジでわからなかったですが、このgifを20回ほど見てたらどんどんわかって来ました。(ちなみに「Click Event: Deposit」を送る部分がスタートで$10に変わる部分が終わりです。)
3. Context API
3-1. Context APIとは
実はReactが基本的に持ってるメインストリージもあります。それがcontextです。Reactの公式ドキュメントではcontextをこう説明しています。
コンテクストは、ある React コンポーネントのツリーに対して「グローバル」とみなすことができる、現在の認証済みユーザ・テーマ・優先言語といったデータを共有するために設計されています。
そしてこのcontextを生成してくれるAPIがContext APIというわけです。
3-2. 基本概念
それではこのContext APIの基本概念についても軽く見てみましょう。
Context
先述した通りcontextはReactが基本的に持ってるメインストリージです。createContextというメソッドを使って生成できます。
サンプルコードで見てみましょう。例えば言語を管理するcontextを作ったとします。
import { createContext } from 'react';
export const LanguageContext = createContext("kr");
このようにcreateContextで生成したcontextオブジェクトにはProviderとConsumerという特別なReactコンポーネントが入ってます。
Provider
Providerはvalue propsを用いて子コンポーネントたちにstateを渡す役割です。Providerの中にあるすべての子コンポーネントはそのcontext内のデータにアクセスできます。
import React from 'react';
import LanguageContext from './LanguageContext'; //上で作ったcontext
import MainContent from './MainContent'; //コンポーネント
import Header from './Header'; //コンポーネント
export const App = () => {
return (
<LanguageContext.Provider value="kr">
<div className="App">
<Header />
<MainContent />
</div>
</LanguageContext.Provider>
);
}
Providerのvalue propは必須なので、書かないとこういうエラーがでます。
Warning: The
valueprop is required for the<Context.Provider>. Did you misspell it or forget to pass it?
これでHeaderとMainContentがcontextにアクセスできるようになりました。基本的にはConsumerでアクセスできます。
Consumer
ConsumerはProviderから渡されたstateをもらって使う役割です。
MainContentのコードがこうだとしてみましょう。
import React from 'react';
import LanguageContext from './LanguageContext';
export const MainContent = () => {
return (
<LanguageContext.Consumer>
{(language) => (
<section language={language}>
//何らかの内容
</section>
)}
</LanguageContext.Consumer>
);
};
Consumer内の関数でstateを使うことができます。Providerのvalueでkrを渡してましたから、このlanguageはkrになってるはずです。
これでも良いですが、もっと簡単に使える方法があります。useContextというhookを使う方法です。
useContext
useContextがやってることはConsumerと同じですが、Consumerで囲まなくても渡されたstateをもらって使わせてくれるhookです。
import React, { useContext } from "react";
import LanguageContext from "./LanguageContext";
export const MainContent = () => {
const language = useContext(LanguageContext);
return (
<section language={language}>
//何らかの内容
</section>
);
};
こちらのほうが綺麗ですね。
要約
- context:Reactが基本的に持ってるンストリージ
-
Provider:子コンポーネントたちに
stateを渡す -
Consumer:
Providerから渡されたstateをもらって使う -
useContext:
Consumerで囲まなくても渡されたstateをもらって使わせてくれるhook
stateが更新される過程
:Providerのvalue propsを更新すると、Consumer or useContextでstateを使ってる子コンポーネントたちに反映される
4. ReduxとContext APIの違い
4-1. Reactの束縛
Reduxはライブラリです。Reactと一緒に使われる場合が多いですが、Reactではないフレームワーク(Vueなど)でも使えます。
Context APIはReactに搭載されている機能です。当たり前にReactではないと使えません。
4-2. メインストレージの数
Reduxのstoreは一つだけです。この一つのstoreですべてのstateを管理します。ロジックを分けたい場合は機能ごとにreducerを分けます。例えツイッターを作るとしたら「ユーザーに関するreducer」、「投稿に関するreducer」などになりそうです。
Context APIのcontextは複数作れます。ですので、機能ごとにcontextを生成することになります。ツイッターの例だったら「ユーザーに関するcontext」、「投稿に関するcontext」などでしょう。
Context APIは機能が増えたら毎回contextを生成するからめんどうだという意見もあります。
4-3. Middleware
ここが一番の違いだと思いますが、ReduxはMiddlewareが使えます。Middlewareが何かというと、こんな感じです。
Reduxの基本概念でactionがreducerに送られると話しました。Middlewareを使うとこのactionがreducerで処理される前に他の作業を追加することができます。例えば、
-
actionをconsoleに表示する(もしくはサーバー側でロギング) - 特定の条件では
actionを無視する - 特定の
actionが発生したらjavascriptの関数を実行させる - 特定の
actionが発生したら他のactionも発生させる
などなどです。Middlewareはよく非同期処理を扱う時に使われ、便利機能を提供してくれます。
context APIはMiddleware使えないので、このような非同期処理の機能は全部直接実装することになるのでしょう。
4-4. 複雑さ
基本概念部分で既に感じたと思いますが、Reduxは概念が多く、ディレクトリもコード量も増やしてプロジェクトを複雑にします。それにReduxを使うためにまた色々なライブラリが必要になり、ライブラリが増えたらバンドルも重くなります。この問題を解決するため、Reduxが自らredux-toolkitというライブラリを開発したぐらいです。
それに比べたらContext APIは割と簡単です。特にライブラリをインストールしなくてもProviderとuseContextだけ理解してたら使えます。
5. 結論
5-1. 何を使うべきか
今までの違いを踏まえたらこのような結論に至ります。
Reduxがオススメ
- アプリケーションの規模が大きい
- グローバルなstate管理機能以外にも多様な機能がほしい(デバッグやロギングなど)
- 非同期処理することが多い
Context APIがオススメ
- アプリケーションの規模が大きくない
- グローバルなstate管理機能だけで十分
- 非同期処理することがあまりない
ReduxはContext APIより機能が多いですが、その分コード量もバンドルサイズも増やすことになります。メリットとデメリットをきちんと確認して採用したほうが良いでしょう。
6. その他
Mediumで「Stop Asking if React Hooks Replace Redux」という記事があります。始め方が面白いですよ。
「Do React hooks replace Redux?」という質問に対する早い答えは「いいえ(not really)」。
より微妙だけれど丁寧な答えは「まぁ, それはあなたが取り組んでいるプロジェクトの種類によりますね」。
実際に私が人々に伝えたい答えは「あなたが何を言っているのか分からない」。「Do React hooks replace Redux?」という質問が根本的に間違っている理由はいくつかある。
その理由をいくつかあげてます。気になる方は読んでみてください。
終わり
この記事…大変だった…📚でもこれでReduxを構造を理解できたので、やっとredux-sagaに進めそうです。
Discussion