redux

Reduxの基礎知識② Reactへの実装まで

今回も引きづつきReduxについて勉強していきます。今回は実際にReactにReduxを組み込む所までやります。

前回の記事見てないよ!という方はこちらをご覧ください→「Reduxの基礎知識① Action,Reducers,Store

 

Reduxのデータフロー

Reduxのデータフローは、前回のFluxモデルで説明したように厳密な単一方向のフローとなっています。

これは、アプリケーションの全てのデータが同じライフサイクルを持っているということであり、アプリケーションの仕組みを理解しやすくする助けとなります。

Reduxのデータフローは、次の4つのステップで行われます。

1. store.dispatch(action)を呼ぶ

Actionsは状態をどのように変更するかを説明するオブジェクトで、storeオブジェクト作成後、store.dispatch(action)を行うことで状態の変更を依頼します。このメソッドはアプリケーションのどこからでも呼ぶことができます。

2. Storeが、指定したReducerを呼び出す

上記のdispatchでStoreにActionsが渡されると、Storeは現在のstateと渡されたactionを引数としてreducerを呼び出します。ここで、reducerは純粋な関数であり、副作用を持たせないことに注意しなければなりません。

// 現在のstate
 let previousState = {
   visibleTodoFilter: 'SHOW_ALL',
   todos: [
     {
       text: 'Read the docs.',
       complete: false
     }
   ]
 }
 
 // Actions
 let action = {
   type: 'ADD_TODO',
   text: 'Understand the flow.'
 }
 
 // 現在のstateとactionを引数に新しいstateを返す
 let nextState = todoApp(previousState, action)

3. rootReducerが複数のreducerの出力を単一の状態にまとめる

場合によっては複数のreducerを作成するかもしれません。その時にreducerをまとめるのがcombineReducers()であり、stateを1つのツリー状のようにまとめることができます。

 function todos(state = [], action) {
   // 何らかの処理
   return nextState
 }
 
 function visibleTodoFilter(state = 'SHOW_ALL', action) {
   // 何らかの処理
   return nextState
 }
 
 // reducerをまとめている
 let todoApp = combineReducers({
   todos,
   visibleTodoFilter
 })

// どちらも新しいstateを返す
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)

// stateは単一となる
return {
   todos: nextTodos,
   visibleTodoFilter: nextVisibleTodoFilter
}

rootReducerから返ってきたstateを保存する

Storeは返ってきた新しいstateを保存し、store.subscribe(listener)で購読状態になればlistener内でstore.getState()で値を取得できます。

store.subscribe(() => {
  console.log(store.getState()) // stateが出力される
})

 

Reactでの実装方法

これまでReduxの概要を説明してきたので、実際にReactアプリケーションに導入していきます。

ReduxはReact専門ということは全くなく、AngularやEmber,jQuery,そのままのJavaScriptにも導入することができます。

React Reduxのインストール

reduxをインストールしただけではReactと繋げることができず、react-reduxをインストールする必要があります

$ npm install --save react-redux

Presentational ComponentsとContainer Components

react-reduxはComponentsをPresentationalとContainerに分けよう!という考えを採用しています。

Presentational Components

どのように見えるか、ということを表現するのが目的となる。Reduxが意識することはない。

基本的にはContainer Componentsからのpropsを使ってコンポーネントを描画する。

基本的にはStateless Functional Components(SFC)となる。

import React from 'react'
import PropTypes from 'prop-types'
 
// propsを利用して表示のみを行う、onClickの中身などは意識する必要はない。
const Todo = ({ onClick, completed, text }) => (
 <li
 onClick={onClick}
 style={ {
 textDecoration: completed ? 'line-through' : 'none'
 }}
 >
 {text}
 </li>
)
 
// propの型を制限している
Todo.propTypes = {
 onClick: PropTypes.func.isRequired,
 completed: PropTypes.bool.isRequired,
 text: PropTypes.string.isRequired
}
 
export default Todo

Container Components

どのように動くか、というロジック部分を記述するコンポーネントとなる。

Reduxのstateもここで購読し、Actionsのdispatchも行う。

ここで出てくるmapStateToPropsとが、Reduxのstateをコンポーネントへ渡すものであり、mapDispatchToPropsがActionsを発行するためのdispatchをコンポーネントへ渡す。

これらはconnect()でコンポーネントに紐づけることができる。

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
 
// stateを利用できるようになったことで処理をかけるようになる
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}
 
// Storeのstateをpropsとして使えるようにする
const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
 
// Storeへのdispatchを関数としてコールバックで渡す
const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}
 
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
 
export default VisibleTodoList

 

Storeをコンポーネントへ渡す

これまででstateやpropsをコンポーネントで使う準備はできましたが、肝心のStoreをコンポーネントに渡す必要があります。

これは簡単な実装方法があり、ルートのコンポーネントを<Provider>でラップし、Storeを渡すことでその子孫要素のコンポーネントは全てStoreを利用することができます。

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
 
const store = createStore(todoApp)
 
// コンポーネントにStoreを渡している
render(
 <Provider store={store}>
 <App />
 </Provider>,
 document.getElementById('root')
)

 

これで基本的なReduxの実装は行うことができました。

公式のTodoListサンプルを作ってみるとわかりやすいので是非チャレンジしてみてください