8
7

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 3 years have passed since last update.

【React】Redux Toolkit のチュートリアルのサンプルコードを読んでみる

Last updated at Posted at 2020-12-06

はじめに

React を習得するまでの軌跡をメモっていく備忘録的な記事です。

Redux Toolkit を使ってみる

2019年にリリースされた React の状態管理のライブラリで従来の Redux をより使いやすくしたものらしい。

Redux Toolkit の公式サイト

公式サイトの Intermediate Tutorial を参考にしました。
サンプルコードのGitHubのリポジトリはこちら

サンプルコードの中身は todo アプリで
仕様としては

  • 一個一個の todo は text(すべきこと) と completed(完了したかどうか) を持つ
  • フッターのボタンで all(全てのTODOを表示), active(完了してないTODOを表示) , completed(完了したTODOを表示) を切り替えることができる

UIは以下のような感じです。

スクリーンショット 2020-12-06 19.55.17.png

実際にソースコードを読む

ファイル構成はこんな感じです。

スクリーンショット 2020-12-06 23.59.53.png

Redux Toolkitの要になりそうな部分だけ取り上げています。

ソースコード中にコメントを入れる形で書いていきます。

index.js
import React from 'react'
import { render } from 'react-dom'
import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'


const store = configureStore({
  // store を定義する
  // rootReducer は createSliceで作った Reducer 達を combineReducerにより合体させたもの
  // (↑ reducers/index.js で定義している)
  reducer: rootReducer
})

render(
  // 子コンポーネントが store に接続できるように
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

TODOを追加したり、完了状態を変更する部分の実装

features/todos/todoSlice.js
import { createSlice } from '@reduxjs/toolkit' // createSlice を使えるようにする

// slice を作成する Redux Toolkitのメイン部分
// State, Reducer, Action を一気に生成する
const todosSlice = createSlice({
  name: 'todos', // slice の名前を設定
  initialState: [], // state の初期値を設定
  reducers: { // reducer を設定 複数の reducer を設定できる
     // prepare のコールバックを用いた書き方
    addTodo: {
      reducer(state, action) {
        const { id, text } = action.payload // action.payload には reducer に渡したい正味の値が入る
        state.push({ id, text, completed: false }) // state に新規の値を追加している
      },
      //prepare のコールバックを使うとstateを更新する前に色々と事前に編集できて便利
      prepare(text) { 
        return { payload: { text, id: nextTodoId++ } }
      }
    },
    // こっちが通常の書き方
    toggleTodo(state, action) {
      // state の中から id に一致した todo を見つける
      const todo = state.find(todo => todo.id === action.payload)
      if (todo) {
        todo.completed = !todo.completed // 見つけた todo の complete を更新する
      }
    }
  }
})

// addTodo と toggleTodo の Action を生成できる関数を export
export const { addTodo, toggleTodo } = todosSlice.actions

// Reducer を export
export default todosSlice.reducer

connect, mapDispatch, mapStateToProps についてはこちらの記事が大変参考になりました。
mapStateToPropsとmapDispatchToPropsの理解の仕方

features/todos/AddTodo.js
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { addTodo } from './todosSlice'

// AddTodo component に props として addTodo の Action を渡すため
const mapDispatch = { addTodo }

const AddTodo = ({ addTodo }) => {
  const [todoText, setTodoText] = useState('')

  const onChange = e => setTodoText(e.target.value)

  return (
    <div>
      <form
      {
        // 送信ボタンが押された時、form の値がなければそのまま return
        // 値があれば addTodoの Action が Reducer にフォームの値を送り、 state が更新される、 
        // その後、フォームは空になる
      }
        onSubmit={e => {
          e.preventDefault()
          if (!todoText.trim()) {
            return
          }
          addTodo(todoText)
          setTodoText('')
        }}
      >
        <input value={todoText} onChange={onChange} />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  )
}

// connectを使って Action を AddTodo に Propsとして送る
export default connect(
  null,
  mapDispatch
)(AddTodo)

表示するTODOの切り替え部分の実装

features/filters/filterSlice.js
import { createSlice } from '@reduxjs/toolkit' // createSlice を使えるようにする

// フィルタリングの状態(全て、完了、未完了)を定義した定数
export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}


const filtersSlice = createSlice({
  name: 'visibilityFilters', // slice の名前を設定
  initialState: VisibilityFilters.SHOW_ALL, // slice の初期値を設定
  reducers: { // コールバックは使わず通常の書き方
    setVisibilityFilter(state, action) {
      return action.payload // 受け取った action の値でそのまま state を更新
    }
  }
})

// Action を export
export const { setVisibilityFilter } = filtersSlice.actions

// Reducer を export
export default filtersSlice.reducer
features/filters/Link.js
import React from 'react'
import PropTypes from 'prop-types'

// filter には VisibilityFilters のいずれかが入る 
const Link = ({ active, children, setVisibilityFilter, filter }) => (
  <button
    // setVisibilityFilter Action で Reducer に filter の値を送信
    onClick={() => setVisibilityFilter(filter)}
    disabled={active} // 現状クリックされているなら disabled に
    style={{
      marginLeft: '4px'
    }}
  >
    {children}
  </button>
)

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  setVisibilityFilter: PropTypes.func.isRequired,
  filter: PropTypes.string.isRequired
}

export default Link

Storeから State を受け取り表示する部分の実装

src/features/todos/VisibleTodoList.js
import { connect } from 'react-redux'
import { createSelector } from '@reduxjs/toolkit'
import { toggleTodo } from 'features/todos/todosSlice'
import TodoList from './TodoList'
import { VisibilityFilters } from 'features/filters/filtersSlice'

const selectTodos = state => state.todos // Store から todos の state を select
const selectFilter = state => state.visibilityFilter //  Store から visibilityFilter の state を select

const selectVisibleTodos = createSelector( // selector を作成
  [selectTodos, selectFilter],
  (todos, filter) => {
    switch (filter) {
      case VisibilityFilters.SHOW_ALL:
        return todos
      case VisibilityFilters.SHOW_COMPLETED:
        return todos.filter(t => t.completed)
      case VisibilityFilters.SHOW_ACTIVE:
        return todos.filter(t => !t.completed)
      default:
        throw new Error('Unknown filter: ' + filter)
    }
  }
)

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

const mapDispatchToProps = { toggleTodo }

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

まとめ

  • createSlice を使うと State, Action, Reducer を一気に作れるので便利。
  • Componentprops として StateAction を渡したい場合は、
    connect, mapStateToProps, mapDispatchToPropsを使うと便利。
  • ReducercombineReducer で結合させて rootReducer を作る
  • configureStorestore を定義する。
  • 作った storeProvider 経由で 子コンポーネントに渡す。
8
7
0

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
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?