考古学 : react-reduxを学ぶ

概要

Hooks対応前のReact(v16.6.3)および、react-redux(v5.1.1)、redux(3.6.0)のプロダクションコードを触る機会があり、色々学んだ内容などをメモる。

2017~2018年あたりのReact + Reduxの実装パターンを学ぶことができました。

当時のReact + Redux構成

ディレクトリ構成

src/
├── actions/
│   └── index.js
├── components/
│   ├── App.js
│   └── TodoList.js
├── containers/
│   └── VisibleTodoList.js
├── reducers/
│   └── index.js
└── index.js

Reduxの基本パターン

Actions

// actions/index.js
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'

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

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

Reducers

// reducers/index.js
import { ADD_TODO, TOGGLE_TODO } from '../actions'

const initialState = {
  todos: []
}

function todoApp(state = initialState, action) {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.text,
            completed: false
          }
        ]
      }
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      }
    default:
      return state
  }
}

export default todoApp

Container Components

// containers/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

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

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

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Presentational Components

// components/TodoList.js
import React from 'react'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo => (
      <li
        key={todo.id}
        onClick={() => onTodoClick(todo.id)}
        style={{
          textDecoration: todo.completed ? 'line-through' : 'none'
        }}
      >
        {todo.text}
      </li>
    ))}
  </ul>
)

export default TodoList

現代のReact + Reduxとの違い

Hooks以前

  • connect() HOCを使用
  • mapStateToPropsmapDispatchToProps を定義
  • Container ComponentsとPresentational Componentsの分離

Hooks以降

  • useSelectoruseDispatch を使用
  • Container Componentsが不要に
  • よりシンプルな実装
import { useSelector, useDispatch } from 'react-redux'
import { toggleTodo } from '../actions'

const TodoList = () => {
  const todos = useSelector(state => state.todos)
  const dispatch = useDispatch()

  return (
    <ul>
      {todos.map(todo => (
        <li
          key={todo.id}
          onClick={() => dispatch(toggleTodo(todo.id))}
          style={{
            textDecoration: todo.completed ? 'line-through' : 'none'
          }}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  )
}

学んだこと

  • Reduxの基本的な設計パターン
  • Container/Presentationalパターンの意義
  • Hooksによる実装の簡略化の恩恵
  • レガシーコードを読むことの重要性

まとめ

レガシーなReact + Reduxのコードを読むことで、現代のReactがいかに進化してきたかを実感できました。

Hooksの登場により、コードがよりシンプルで読みやすくなったことを改めて認識できました。