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
value
prop 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