React Redux Basic Tutorial 俺なりのまとめ

## Reduxとは

facebook社が提案した`flux`を元に、javascriptでアプリのステート管理を行うためのツール。
各コンポーネントで持っていた`state`や関数を`store`や`reducer`と呼ばれる場所で持つことで、全てのコンポーネントからアクセス可能となる。

### 必要性

大規模なアプリケーションになり、stateが多く存在するときなどに導入するといいらしいです。
stateが多くなればなるほど、バケツリレーが大量に発生し、エラーが発生する可能性が大きくなってしまう為です。
このへんは検索すればたくさん出てくるので、`redux`のフローなども含めて調べてみてください。

### 本記事の目的

僕自身アルバイトでこれからReactを書ける環境に行けることや、製作中の個人ブログのフロント部分をReactで書くために学びたいと思っていたのでメモしたいと思い、書きました。  
初めは個人ブログのフロントを`flux`も使わずに書いていたのですが、同じようなstateが複数出てきたときに管理が難しくなったため、`flux`か`redux`を用いることに決めました。  
`redux`に決めた要因としては、facebook社が提供する`flux`が２年前くらいから更新が止まっているとの情報から、学習コストは多少高くても`redux`を覚えてしまおうというところです。  
では、見ていきましょう。

## 導入

### 完成図

<img width="421" alt="スクリーンショット 2019-07-05 7.39.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308296/4a26d26f-86ba-2f5c-221f-2022629ec439.png">

> [Basic Tutorial: Intro · Redux](https://redux.js.org/basics/basic-tutorial)

### 環境

環境はこのような感じです。

```
macOS mojave
node v12.6.0
redux ^4.0.1
yarn 1.17.0
```

本記事では`yarn`を用います。`npm`の方は適宜読み替えてください。
`yarn`は`brew`でインストールすることが推奨されているようです。

### create-react-app

まず`npx`を用いてプロジェクトを作成します。

```
npx create-react-app todo-tutorial-redux
```

次に、一旦作成されたプロジェクトの起動確認をしていきます。

```
cd todo-tutorial-redux
```

```
yarn start
```

ロゴが表示されたらokです。

次に、、必要なツールを入れていきます。
`yarn add redux react-redux`

これで準備は完了です。

## Container ComponentsとPresentational Components

いきなりですが、公式tutorialの順番とは大きく入れ替わって、まずは`Container Components`と`Presentational Components`について見ていきます。
`redux`ではコンポーネントの種類を大きくこの２つに分けており、それぞれのルールに則って作ることで、再利用性の高いコードが書けるようになります。

> [ComponentとContainerについて - Qiita](https://qiita.com/tenta1111/items/7b4d2ea1a4d1d2336032)

こちらを参考にさせていただきました

### Presentational Component

このコンポーネントは見た目を担当するコンポーネントで、reactで普段用いるコンポーネントと変わりありません。

- stateを基本持たない（持つ場合はデータではなく、UIの状態）
- `Actions`, `Store`（reduxの他の要素）に依存しない
- 基本的に`functional components`として書かれる

### Container Components

`redux`の`connect`という機能を用いることで、ロジックに関与することができるコンポーネントになります。
通常、このコンポーネントはスタイルを持たず、下位のコンポーネントのデータソースとして機能します。  
`mapStateToProps`と`mapDispatchToProps`というところがキモで、

- `mapStateToProps` : `store`で管理する`state`を`props`として扱うことができる。
- `mapDispatchToProps` : `Actions`で定義した関数を`props`として扱うことができる。

これを使って、`redux`管理をしているものを`Presentationl Components`で表示したり、関数を実行したりできます。

### ディレクトリ構造

公式tutorialでは、`Presentational Components`を`Components`ディレクトリに、`Container Components`を`Containers`ディレクトリに分けています。
しかし、これらを分けることは必須ではなく、まとめて記述しても問題ありません。

## Actions

ここからはtutorial通りに進めていきます。`Actions`は`store`に対しての動作を書くところです。
何かが起きたときに、起きたことによって何をするか、そのときに用いるデータと共に定義しておき、`Reducers`へ報告します。（`Actions`の時点では`store`の状態は変化しません。）
そして、これを実際に呼び出すところが`Container Components`です。

`Actions`は、`action types`というユーザが行った動作を文字列で指定したものを持ちます。この文字列は次に説明する`Reducers`と対応します。
役割としては、`Actions`で発火、`Reducers`で処理をする際の命令を投げる先の指定といったところです。

```javascript
export const ADD_TODO = 'ADD_TODO'
```

そして、`action creators`で`action types`で定義したデータとそのアクションに必要なデータをまとめます。`Reducers`はここから呼び出されます。

```javascript
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
```

単純な関数として記述されていることがわかります。ユーザがTODOを追加したとき、その`action types`とその動作に必要なデータとして`text`を返しています。

### actions.js

```js:actions.js
/*
 * action types
 */

export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * other constants
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action creators
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}
```

## Reducers

`Reducers`はアクションから何が起きたかをしらされたときに実際に行なう処理を書きます。  
その処理は、以下のように
`(previousState, action) => newState`
過去の`state`と処理の内容から新しいものを生み出すというものです。

`Reducers`の注意点として、

- 引数が不自然に変更されてはいけない（同じ引数を渡したら常に同じ結果を返す）
- 非同期処理など副作用を生み出すものを書けない(`redux-thunk`や`redux-saga`といった`MiddleWare`を使わないといけない)
- `Date`や乱数などの`non-pure function`は使用できない

という点があります。

### Reducersの返す値

`Reducers`はアクションを受け取ってなにか処理を行い、反映させます。
しかし、初回の表示の際に表示するデータが定義されていない場合、うまく動かないことがあるため、アクションがない場合には`initialState`を定義しておいてそれを返すようにします。

```javascript
function todoApp(state = initialState, action) {
  // For now, don't handle any actions
  // and just return the state given to us.
  return state
}
```
重要なのは`state = initialState`と`return state`です。

実際に処理のあるパターンを見てみましょう。

```javascript
import {
  ADD_TODO
} from './actions'

...

function todoApp(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}
```

`action types`をインポートして、それを元に`switch`で処理を選択しています。
先程書いた初期データを返す命令は`default`で定義されます。

1. この`reducers`で`ADD_TODO`のアクションが呼ばれたら
2. `state`を今の`state`(`...state.todos`)と送られてきた`text`を追加したものをとして更新して返す

といった処理をしています。

### Reducersの分割

処理を書いていくうちに、規模が大きくなったり機能が入り混じったりして、見通しが悪くなってしまうことが出てくると思います。  
以下の例では、TODO一覧をフィルター表示するためのものとTODOを追加・トグルするものというもので分けたほうが見やすそうです。

```javascript
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}
```

`redux`は`reducer`を１つしか返すことができません。しかし、複数の`reducer`をまとめ上げたものを返すことは可能です。そのための機能として、`combineReducers`というものがあります。  
以下のように、`todos`と`visibilityFilter`という`reducer`に分ける場合、

```javascript
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}
```

このようにまとめることができます。

```javascript
import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})
```

### reducers.js

```javascript:reducers.js
import { combineReducers } from 'redux'
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp
```

## Store

`Store`はアプリケーションにつき１つしか存在しません。
役割として、

- `getState()`で`state`へアクセスできる
- `dispatch(action)`で`state`を更新できる
- `subscribe(listener)`でリスナーを登録できる
- `subscribe`で登録した関数で`unsubscribe`できる

```javascript
import {
  addTodo,
  toggleTodo,
  setVisibilityFilter,
  VisibilityFilters
} from './actions'

// Log the initial state
console.log(store.getState())

// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() => console.log(store.getState()))

// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// Stop listening to state updates
unsubscribe()
```

<img width="458" alt="zMMtoMz.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308296/87550136-f098-00e8-3a96-ec03f26c64b3.png">
<img width="421" alt="スクリーンショット 2019-07-05 7.39.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308296/47d34b3a-7c3b-b116-6c25-7a5a58f5bf3a.png">


### store.js

`combineでReducers`でまとめた`reducers`を元に`store`を作成します。

```javascript:store.js
import { createStore } from 'redux'
import todoApp from './reducers'

const store = createStore(todoApp)
```

## Component

以下の設計でTODOListを作成します。

#### Presentational Components

- TodoList : TODOのリスト表示
  - todos :  `{id, text, completed}`という構成の配列  
  - onTodoClick(id: number) : 未完了・完了をtoggleする
- Todo : TODO単体
  - text: string : 本文
  - completed: boolean : TODOの状態
  - onClick() : callback
- Link : callback
  - onClick()
- Footer : 表示フィルターの選択
- App : root component

#### Container Components

- VisibleTodoList : TODOのフィルター後のデータを保持する
- FilterLink : 現在`Link`で選択されているフィルターからフィルターを行う
  - filter: string

#### Other Components
- AddTodo : Addボタン

### components/Todo.js

```javascript:components/Todo.js
import React from 'react'
import PropTypes from 'prop-types'

const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
)

Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo
```

単純にTodoを表示し、`onTodoClick(index)`を実行を渡します。

### components/TodoList.js

```javascript:components/TodoList.js
import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map((todo, index) => (
      <Todo key={index} {...todo} onClick={() => onTodoClick(index)} />
    ))}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.isRequired,
      completed: PropTypes.bool.isRequired,
      text: PropTypes.string.isRequired
    }).isRequired
  ).isRequired,
  onTodoClick: PropTypes.func.isRequired
}

export default TodoList
```

`todos`を全て表示します。


### container/VisibleTodoList.js

```javascript:container/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

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)
  }
}

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

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList
```
`TodoList`で表示に用いている`todos`をフィルターしています。
`filter`の値によって、`getVisibleTodos`で`todos`の内容が変わっていることがわかると思います。  
`mapStateToProps`は`getVisibleTodos`に現在の`todos`とフィルターの種類を渡すことで、新しい`todos`を定義しています。  
`mapDispatchToProps`は`Todo`コンポーネントがクリックされたときに発火し、`toggleTodo`を`dispatch`します。

**また、`connect`を用いて、`TodoList`コンポーネントで定義した関数を使えるようにしています。**

### components/Footer.js

```javascript:components/Footer.js
import React from 'react'
import FilterLink from '../containers/FilterLink'
import { VisibilityFilters } from '../actions'

const Footer = () => (
  <p>
    Show: <FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
    {', '}
    <FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
    {', '}
    <FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
  </p>
)

export default Footer
```
フィルターの`action types`を`FilterLink`へ渡しています。

### components/Link.js

```javascript:components/Link.js
import React from 'react'
import PropTypes from 'prop-types'

const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }

  return (
    <a
      href=""
      onClick={e => {
        e.preventDefault()
        onClick()
      }}
    >
      {children}
    </a>
  )
}

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

export default Link
```

選択されている状態の場合はただの`span`で、選択されていない場合は`onClick`を実行する`a`タグを出力する。

### containers/FilterLink.js

```javascript:containers/FilterLink.js
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter))
    }
  }
}

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

export default FilterLink
```

まず、`ownProps.filter`は`Footer`で選択されたフィルターを持ちます。
`Link`がクリックされると、`setVisibilityFilter`が`dispatch`されます。
そして、押されたリンクは`active`を持ち、そのリンクだけが`span`で表示されるようになります。  

参考：reducer部分

```javascript:reducer.js
...

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

...
```

### containers/AddTodo.js

```javascript:containers/AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault()
          if (!input.value.trim()) {
            return
          }
          dispatch(addTodo(input.value))
          input.value = ''
        }}
      >
        <input
          ref={node => {
            input = node
          }}
        />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo
```

このコンポーネントは`Container Components`ですが、スタイルも入っている例です。
`AddTodo`は`input`の値で変わるため、`let`で宣言されています。
送信されたら、入力内容を`addTodo`に`dispatch`しています。

### components/App.js

```javascript:components/App.js
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App
```

### index.js

```javascript:index.js
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)

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

`index`では、`Provider`を使って`store`を使えるようにする必要があります。


## まとめ

読んでみると少し理解が深まった気がしますが、非同期通信を用いた方法や、複雑な処理を行う場合、自分で書くためにはもう少し勉強しなきゃなと感じました。  
次は、非同期通信を用いたパターンについてまとめる予定です。
ご指摘、アドバイスがあれば宜しくおねがいします。