#Reduxとは
かなり長いこと保留していたRedux入門、ようやく書きます。。
この回ではコードそのものはできるだけ説明せず、概念を説明する形にしたいと思います。
というのも、コードそのものよりこの考え方の理解がちょっと難しく、ここを適当にしてコードを見てしまうと余計に複雑に思えてしまうので、わりと外せないポイントだったりします。
Reduxの役割
Reactで複数コンポーネント構成のものを開発をしていると、コンポーネント間のstateの扱いで困ってくることがあります。
- このコンポーネントでセットしたstateを別のコンポーネントで呼び出したい
- 別のコンポーネントからStateの値を追加変更したい。
この場合、コンポーネントが浅い親子関係にあればpropsで継承するなり関数を引き渡して実現させることも出来ますが、親子関係が深い場合や、そもそも親子関係のないコンポーネント同士は値や関数のバケツリレー地獄になったり、無理矢理の親子階層が作られたりして、キレイな設計とはほど遠いものになっていきます。
そのときの解決方法の1つが「Reduxを導入すること」です。
Reduxを使うと、
- 各コンポーネント共通で参照できるState(=Store)が利用できる
- そのStoreをどのコンポーネントからでも変更可能な仕組みが用意されている
というメリットがあります。要は読み込みも追加変更も可能な「大きなState」が使えるようになる、というイメージで、これによって複数コンポーネントを持つアプリがより作りやすくなります。
基本概念
Reduxを使う上で理解しておく概念は以下の3つです。
- StoreはComponentの枠を超えて使える大きなState
- Storeを書き換えるにはAction→Reducerの処理を”必ず”通過しなければならない
- Reducerではデータの加工は行わない。データの加工はAction 内で完結させる
…何のことだかさっぱりだと思いますが。。
Redux導入の前にReact単体でのStateの流れを理解しておかないとよく分からなくなるので、まずReact(Component単体)のおさらいから。
React(Component単体)
Component
:Component内でのトリガー動作によって、内部のActionメソッドを実行
↓
Actionメソッド
:外部問い合わせなどを行い、情報をあつめてStateに設定する新たな値を用意する
↓
Stateの書き換え
:setStateの動作
↓
Stateの変更
:setStateの指示によって変更された値を格納
↓
render
:stateの内容に応じて表示書き換え
この流れをコード化したものがこちら。
内容はHello!をthank you!に書き換えるだけという、何のメリットがあるのか分からない例文です。笑
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
message : "Hello!"
}
}
// Actionメソッド
stateChangeAction(){
// Stateの書き換え
this.setState({message : 'thank you!'});
}
render(){
return(
<div>
<p>{this.state.message}</p> {/* 内容の反映 */}
<button type="button" onClick={()=>this.stateChangeAction()}>Change!</button> {/* トリガー動作 */}
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Redux
次に全く同じ処理をReduxで実現させた場合の流れです。比較のため、単体のコンポーネントですがあえてReduxを導入して考えてみます。
Component
:ボタンクリックなどトリガー動作によって、内部でActionを実行
↓
Action / Action Creator
:外部問い合わせなどを行い、情報をあつめてStoreに設定する新たな値を用意する
↓
Reducer
:Storeの値を書き換える
↓
Storeの変更
:Reducerの指示によって変更された値を格納
↓
Component(render)
:storeの内容に応じて表示書き換え
流れだけ見ればReactのコンポーネント単体と全く同じです。
何が違うのと思いながら、次のコード化したものを見てください。
import React from "react";
import ReactDOM from "react-dom";
import { Provider, connect } from "react-redux";
import { store } from "./store.jsx";
import { change } from "./action.jsx";
class App extends React.Component {
stateChangeAction() {
this.props.change("thank you!");
}
render() {
return (
<div>
<p>{this.props.message}</p>
<button type="button" onClick={() => this.stateChangeAction()}>
Change!
</button>
</div>
);
}
}
const mapStateToProps = store => ({
message: store.message
});
const mapDispatchToProps = dispatch => {
return {
change: value => dispatch(change(value))
};
};
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App);
const rootElement = document.getElementById("root");
// Rendering
ReactDOM.render(
<Provider store={store}>
<AppContainer />
</Provider>,
rootElement
);
import { createStore } from "redux";
import fromReducer from "./reducer.jsx";
const initialState = {
message: "hello!"
};
export const store = createStore(fromReducer, initialState);
// Action名の定義
export const CHANGE = "CHANGE";
import * as Constants from "./constant.jsx";
// Action Creators
export function change(value) {
return {
type: Constants.CHANGE,
message: value
};
}
export default function fromReducer(state, action) {
switch (action.type) {
case "CHANGE":
return Object.assign({}, state, {
message: action.message
});
default:
return state;
}
}
入力と結果で見る処理としてはコンポーネント単体と全く同じものですが、index内でStateを使っていないことから、Storeとしてデータ領域を分離していることが分かります。
それにしてもコード量がものすごく増えました。。しかも複数ファイル。(実際は複数ファイルでなくても書けるのですが、実用では分割するのが通常ですし、内容も分かりにくくなるのであえて分けています。)
そうなんです、Reduxは出来ることが増える分、コード量が格段に増えるデメリットがあります。
本当にReduxを使うべきか
Reduxを導入する一番のメリットは「Componentの枠を超えて使える大きなState(=Storeという)を使える」ことです。
大きなStateである「Store」を使ったほうがいい場合とは、下記のような場合が考えられます。
- 複数ページに渡って情報を参照、変更したい場合(データベースから取得した一覧データを各ページで参照したい、とか)
- コンポーネントの階層が深くなって、propsとその変更メソッドのバケツリレー地獄に陥ってしまいそうな場合
- 複数人で分散して開発を進めるうえで、共通のデータ保管領域を設定した方がやりやすい
たぶんこの3点くらいで、逆にこれが不要な場合は、前述の通りReduxは導入しない方が分かりやすくシンプルに書けます。
いきなり少し否定的な言い方をしてしまいますが、というのもReduxはうまく使えばとても便利なものですが、実は使い方をミスるとすぐ事故ります。。
Reduxのデメリットを上げると、
- 状態変更の度にComponent→Action→Reducerと毎回複数のソースファイルを横断するため、見通しが悪くなる
- 複数のコンポーネントからの指示でStoreが書き換えられることがあるので、状態変更→レンダリング書き換えの流れが1対1でなくなり、分かりにくくなる(ことがある)
- StoreとStateのデータ管理するため、データの置き場所が複数箇所になり管理が煩雑になる。
という感じです。要はReduxは複雑さが増すため分かりにくくなる傾向になります。
先のコード例を見てもらえばわりと一目瞭然かと思いますが、React単体のコンポーネントのみで実現できることまでReduxに置き換えると、単に複雑になるだけでデメリットのみが増えます。
なので、**「もしReduxを使わない書き方で実現できるならば使わないほうがベスト」**です。
ただこれは全く使わないという意味ではなくて、Reactの不便なところだけReduxを使えばいいという見方であって、
**「データ毎にStateとStoreどちらで扱うべきかを判断して振り分ける」**という形で使えばとても便利なものになります。
先ほど挙げたデメリットで「StoreとState両方使うこと」と書いたので矛盾しているような気もしますが、
よく考えず棲み分けをごちゃごちゃにしてしまうとうまく管理できなくなるものの、しっかり見極めが出来ていれば影響範囲が明確に整理され、逆に管理しやすくなります。
以上のことを踏まえて、(個人的な見解ですが)Reduxを導入するうえで押さえておきたいポイントは以下になります。
- 特定のコンポーネント内で完結するデータ(=そのデータが他のページに影響を及ぼさないもの)はStateで完結させる
- 親子関係のある、浅い階層のデータ引き渡しの場合は、できるだけReduxは使わずpropsで対応する(関連性を明示する意味もあります)
- Storeを使うしかないデータ(どうしてもComponentの枠を超えて管理しなければいけない事情がある場合)のときにだけ、Reduxを使う
まとめ
今回Redux入門と書いておきながらReduxのメリットを全く体感させず「できるだけ使わない方が…」という締めです。。
というのも、実際ソースを書いて体感した経験としては、Reduxを導入すれば今まで出来なかったことが実現できるというメリットがありつつも**「うまく使わないと事故る」**とういうことに尽きます。
実際私自身が最初に書いたReduxでは、浅い考えのまま導入した結果ほぼすべてをStore管理にしてしまい、規模が大きくなってきたときにはもう収集つかなくなりました。
次回からは具体的なコードと書き方の説明に入りたいと思います。
そして「Reduxすばらしい」と思えるようなこともちゃんと書きたいと思います。笑