317
295

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React Redux の難しかった点をできるだけシンプルに図解

Last updated at Posted at 2018-08-23

はじめに

この記事はだいぶ古くなっており、現在はReact Hooksを使ったReduxとComponentの繋ぎこみ方法があるようです。この記事ではそのあたり触れられてないので注意です。(Reduxの基本的な記事としてはまだ使えると思います、、、思いたい。)

React-Reduxを人に教える機会があるのですが、中々キャッチアップが難しいFWです。
自分自身もReact自体未経験のところから、それなりに規模のある開発に参画したので、
その時に「ここが難しかったなあ」というところを自分なりに図解してみたいと思います。

前提

  • ReactとReduxの接続にはreact-reduxのconnectを使うこととします。

この記事で伝えたいところ

自分が難しく感じたのはこのあたりでした。

  • (ソースが色々あって)全体の流れがよくわからない
  • actionを起こすとreducerが反応する仕組みがよくわからない
  • mapStateToPropsやmapDispatchToPropsがおまじないにしか見えない

読み終えたときにこれらが理解できることを目指して書いていきたいと思います。
なんとなく「こうすれば一通り動くのはわかるけど理由が知りたい」人向けです。

Reduxだけの場合

まずReactを省略してReduxだけの概念を図解します。

Reduxとは、アプリケーションの状態(State)管理を容易にするためのフレームワークです。
次の図を見ると矢印の向きが1方向になっているのがわかります。
このように単一方向の状態管理を実装するためのStoreという層を提供してくれます。

image.png

全体の流れは以下です。(図中の数字と対応付けてます)

  1. View(画面)のInputをもとに、Action Creator(*)を使ってactionを生成する。
  2. Storeに対してactionをDispatchする。
  3. ReducerがDispatchに反応して、actionをもとにStateを更新する。
  4. ViewはStoreからStateを参照する。(getStateと言う取得関数があります。)

これの繰り返しで状態を更新・管理していきます。

(*) actionはViewで直接生成したりしてもよいのですが、大抵Action Creatorがactions等のjsにまとめて定義されます。こんなやつです。

// create addTodo action
function addTodo(text) { 
    return { 
        type: ADD_TODO, 
        text
    } 
}

(このaction creatorとactionがごっちゃになりがちなのも個人的に最初難しく感じるところでした)

図中のMiddlewareについては今は触れないでおきます。

  • Stateの更新はReducerが行う。
  • Reducerに働いてもらうためには、ActionをStoreにDispatchする。
  • Stateを取得するには、StoreからgetStateする。

という点を抑えておきます。

React-Reduxの場合

次に、実際にReduxをReactで使う場合です。

ReactとReduxを繋ぐContainer

ReactとReduxを繋ぐComponentをContainer Componentと呼びます。
以下のようなイメージになります。

image.png

  1. Container Componentはthis.props.anyAction() のようにしてactionを発行できるようになります。
  2. 同様に、this.props.anyStateのようにしてStateを参照できるようになります。

前後しますが、コード例としては以下のようになります。

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

class MyContainer extends Component {
  // 省略
}

const mapStateToProps = (state, ownProps) => {
  return {
    todos: state.todos
  }
}

const mapDispatchToProps = { // あえて関数ではなくオブジェクトにしておきます。
    addTodo
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyContainer)

この時のmapStateToPropsとmapDispatchToPropsを
理解できていればreact-reduxについてはバッチリだと思います。

mapStateToPropsとmapDispatchToProps

「Reduxだけの場合」に記載した以下のポイントを思い出しましょう。

  • Reducerに働いてもらうためには、ActionをStoreにDispatchする。
  • Stateを取得するには、StoreからgetStateする。

connect(mapStateToProps, mapDispatchToProps) は、
ReduxのStoreをReact Componentの世界に持ち込んでくれます。

以下のようなイメージです。

image.png

  1. mapStateToPropsは、Store.getState()のような役割をして、ComponentのpropsにStateの中身を詰め込んでくれます。
  2. mapDispatchToPropsは、ActionCreatorをラップした、actionをStoreにDispatchしてくれる関数をpropsに詰め込んでくれます。

特に2点目はたまに理解できてない場合があります。
たとえば、上の方にあるコードで、importしているaddTodo()をpropsを通さず直接実行してもReducerは反応しません。
それは、単純にActionCreatorだけを実行しても、Dispatchしたことにならないからです。

mapDispatchToPropsは関数でも書くことができて、そちらを見るとわかりやすいです。
(自分が今まで見たコードではいつも単純なオブジェクトでした。。)

const mapDispatchToProps = dispatch => ({
  // actionCreatorで生成したactionをdispatchする関数を作っている。
  // これがMyContainerに渡すaddTodoになる。
  addTodo: text => dispatch(addTodo(text)) 
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MyContainer)

このような形で、Storeへのアクセスを私たちが意識しなくていいように
React Componentへ、文字通り繋いでくれるのがconnect(mapStateToProps, mapDispatchToProps)関数になります。
※connect関連のapiには、例には書いてない引数や機能もあるので興味ある方はreact-reduxのGitHubを読んでみてください!

そして、すべてのReact ComponentをContainer化してしまうと
Reduxへ依存し過ぎてしまい再利用性のあるComponentが書けなくなってしまうため、
大抵以下のようにpropsを使って、子のComponentへ関数や値を渡して利用します。

image.png

Provider

でも、ここまでのコード例で一度もStoreなんて出てきてないのに
どうやってStoreを利用できるの?と思うかもしれません。そういった疑問を持てるのは素敵です。

react-reduxからのほぼ引用になりますが、下記のようにProviderを使うことで
ラップされたComponent達がStoreとconnectできるようになります。

// 主旨じゃないimportとかは省略してます

import { Provider } from 'react-redux'

const store = createStore(myRootReducer)

ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent/>
  </Provider>,
  document.getElementById('root')
)

ここまで知っていればReduxをReactで使う方法はほぼ全てわかったと言って良いと思います。お疲れさまでした。

実際にはreact-routerと一緒に使うことも多いと思います。real-world exampleが参考になります。

Middlewareについて

図中で放置されていたMiddlewareについてもさっと触れておきます。
Storeについて理解できていればあまり難しいことはありません。

  • actionがReducerにたどり着く前に、actionを見て処理を実行できる場所
  • Middlewareは、Storeにアクセスできる

この2点だけ抑えておけば大丈夫だと思います。

Reducerへ辿りつく前に、その場でactionを捨てたり、作り直したり、関心がない場合は何もしないこともできます。
Storeにアクセスできるので、今のStateを参照したり、その場で新たなactionをDispatchしたりできます。

例えば、react-redux公式のreal-world exampleでは、
シンボル化されたCALL_APIactionを拾って、API実行の結果に応じて
request, success, failureに分けてactionをReducerへ流すようにしています。

終わりに

繰り返しになりますが、

  • Reducerに働いてもらうためには、ActionをStoreにDispatchする。
  • Stateを取得するには、StoreからgetStateする。

この点が理解できていればReduxを難しいと感じなくなるのかなと思います。
例えば、redux-thunkというライブラリがありますが、これもaction creatorの中で
Storeのdispatch、getStateができるだけと考えれば難しくありません。

うまく説明できたかわかりませんが、これからReact/Reduxの世界へ飛び込む人の助けになればと思います。
(そして将来また人に教えるときの自分の助けにもなるといいな。。。と思って書きました。)

以上です。

317
295
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
317
295

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?