Reactでは、コンポーネントの状態(State)が個別に扱われていますが、ログイン機能など、アプリケーション内でグローバルに持たせたいStateもあります。そういったケースでグローバルなStateを管理する機能を提供するのがReduxです。ReduxはFacebook社が提唱するFluxアーキテクチャに影響を受けています。
<値の保管場所は一つだけ>
すべてのデータは「ストア」と呼ばれるところに保管されます。これは、アプリケーションごとに一つだけ用意しておき、すべての値はこの「Store」の中で保持されます。Storeが一つのオブジェクトに集約されてるので、デバッグもしやすくなります。
<値は読み取り専用>
「ストア」に用意されている値は、参照だけ可能で、変更は許可されていません。
変更する手段は、どんなイベントが起こったかを表現するActionのみです。
<変更は別途、関数で行う>
値を作成し直す純粋関数を用意することで、ストアの更新が可能となります。
FluxアーキテクチャのStoreにあるStateを変更する責務を切り出したものとして、Reducerという副作用の無い関数で行ます。前の状態とActionを引数にとって、新たなStateを返します。
Reducerを抽象化すると以下の式で表現されます。
PrevState(値の前の状態)とActionを引数にとって、新たなStateを返します。
(prevState, action) => newState
色々と意見があるかと思いますが、Stateの見通しを良くし、どこからでもグローバルなStateを参照/変更可能にするためと考えています。
ReactのみでStateを管理しようとすると、Prop drilling問題と呼ばれる、Stateのバケツリレーが親コンポーネントから子孫コンポーネントの間で発生します。これは、コンポーネントの階層が深くなってくると表出する問題で、コンポーネント間でStateを渡すためのデータ受け渡しのインターフェースを余計に作らなければいけないコストが発生します。
Reactのコンポーネント内のみで、Stateを管理するのに比べ、Reduxを導入するとアプリケーション内で、Globalに一つ用意されたStoreでStateを管理することができます。そしてStore内のStateを更新したい場合、決められた手続きを踏めば、どのコンポーネントからでも、Stateを変更できることが出来るようになります。また、モジュールの疎結合も実現出来るようになるという点が、Reduxを導入する理由になるかと思います。
Fluxは単方向データフローを実現するためのフロントエンドのアーキテクチャです。 登場する用語として以下が挙げられます。
Store アプリケーション全体で参照したいデータの保管場所
Action イベントにおける『対象のデータをどうしたいか』を表現したもの
Dispatcher Actionの種類を判別し、紐づいたStoreの更新処理を行うもの
ざっくりですが、以下のようなフローで単方向データフローの中で値の更新処理を行ます。
① 『対象のデータをどうしたいか』という意図を表現したオブジェクト(Action)を、Dispatcherに対して、発行する。
② DispatcherがActionの種類を判別し、それに対応する手順に従って Storeの値の状態を変更する。
③ Storeが書き換えられて、View(コンポーネント)に反映される。
処理の流れは上記図の通りです。Fluxは、__単方向データフローを徹底__することで、破綻しにくく予測可能な状態でアプリケーション開発が進められることを目指したものです。
Store
値を保管しておくものです。ここに保管される値は「ステート」と呼ばれます。
Provider
ストアを他のコンポーネントに受け渡すための仕組みです。
Reducer
ストアに保管される「ステート(値)」を変更するための仕組みです。
ReducerはFluxでなく、Reduxで登場するモジュール です。
FluxのStoreの中に保管されているStateの変更をする責務を切り出し、DispatcherとStoreの相互依存を解決し、従来のFluxパターンをよりシンプルに書けるようにしています。
Reducerで呼び出す処理は常に、新しいステート(値)を最後に返すことを覚えておきましょう。
Actionはどんなイベントが起こったかを表現するプレーンなオブジェクトです。
export const increment = () => {
return {
type: "INCREMENT"
}
}
export const decrement = () => {
return {
type: "DECREMENT"
}
}
Reducerは副作用のない純粋関数です。
PrevState(値の前の状態)とActionを引数にとって、新たなStateを返します。
const reducer = (state, action) => {
///何らかの処理
}
__第一引数のstate__には、Storeに保管するState(値)を指定します。これは整数やテキスト、複数の値を扱いたい場合はオブジェクトを用意します。
__第二引数のAction__は、Reducerを実行する際に渡される値です。これはReducerが実行される時の内容をまとめたプレーンなオブジェクトです。
Reducer内では、要件によって、さまざまな処理を記述することになるのですが、この引数のアクションを使って、どのような処理を行うかを定義していきます。(簡単な例だと、increment,decrement,resetなど...)
必ず用意されるのは、「type」というプロパティで、これは、呼び出し時のactionの種類を示す値になっています。レデューサーは、typeを使い、「このtypeの時は、この処理。このtypeはあの処理。。。」と処理を振り分けていきます。
//Reducer
const counter_reducer = (state = state_value, action) => {
switch(action.type) {
case 'INCREMENT':
return{
counter: state.counter + 1,
message: 'INCREMENT'
}
case 'DECREMENT':
return {
counter: state.counter - 1,
message: 'DECREMENT'
}
default:
return state;
}
}
そして、新たにStoreに保管するState(値)を用意して、それをreturnすると、StoreのなかのState(値)が、更新されます。
reducer関数の決まりとして、以下の3点は覚えておきましょう。
1.引数はstateとactionの二つ
2.actionは、typeでstateをどう変更させるかを指定する
3.戻り値は、変更後のstate
Reducerが用意できたら、Reduxの核となるStoreを作成します。 改めて書くと、ストアは一つのアプリケーションにつき、一つのみです。
const store = createStore(reducer, initialState);
そして初期化したStoreを最上位のコンポーネントでProviderを使って、propsとして渡す必要があります。これによって、初めて子コンポーネントの中で、Reduxの提供する機能が使えるようになります。これで複数の子コンポーネントからも、Storeの値にアクセスできるようになります。これはお約束事です。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { reducer, initialState } from 'reducer';
import App from './App';
const store = createStore(reducer, initialState);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
上記を用意して初めて、Reduxを利用する準備が整います。
コンポーネントに、Actionをインポートして、viewから発行したactionはdispatcherによって、Reducerに渡されます。
function App() {
const count = useSelector(state => state.count);
const dispatch = useDispatch()
return(
<>
<div>{count}</div>
<button onClick={()=> dispatch(increment())}>click</button>
<button onClick={()=> dispatch(decrement())}>click</button>
</>
)
}
Reduxを導入すると多くのファイルを作成する事になります。実際には、『Duck』や『re-ducsk』パターンを取り入れながら開発が進むことになると思います。
簡単にReduxのディレクトリ構成の例も書いてみたので、興味があったら参照してみて下さい。
Redux導入時のディレクトリ構成例