ゼロから学ぼう！Reduxの基本とその使用方法


どうもこんにちは、たくびー(@takubii)です。
最近、Reduxのチュートリアルをハンズオンで実践しました。
今回はそこで得たReduxの基本的な使い方について書いていきたいと思います。
※なお、今記事ではチュートリアルのPart4くらいまでの情報を取り上げます。Part7,8のRTK Queryについては触れません。

最終的には以下のようなアプリを作成できます。
公式チュートリアルに記載されていたSandboxを添付します。

https://codesandbox.io/embed/github/reduxjs/redux-essentials-example-app/tree/tutorial-steps/?fontsize=14&hidenavigation=1&theme=dark

## チュートリアルの所感
Reduxのチュートリアルの通してやってみた感想としては、自分が今Reduxを使うのに必要としている部分まで進めれば十分だなと感じました。
私が個人的に感じたReduxのチュートリアルの大まかなまとまりを以下にまとめます。

[Part1](https://redux.js.org/tutorials/essentials/part-1-overview-concepts)、[Part2](https://redux.js.org/tutorials/essentials/part-2-app-structure)
上記の2つのセクションはcreate react appのReduxテンプレートで作成できるカウンターアプリを見ながらReduxの説明をしています。
この2つのセクションでは手を動かさなさいので人によっては退屈に感じるかもしれません。

[Part3](https://redux.js.org/tutorials/essentials/part-3-data-flow)、[Part4](https://redux.js.org/tutorials/essentials/part-4-using-data)、[Part5](https://redux.js.org/tutorials/essentials/part-5-async-logic)
上記の3つのセクションは実際に手を動かしながらRedux上でデータの保存や取得を行っています。
正直ここまでやればReduxについては十分な知識が得られると思います。
アプリもほぼ完成系になるので、実際のアプリに使用できると思います。

[Part6](https://redux.js.org/tutorials/essentials/part-6-performance-normalization)
このセクションではSliceを作成するのをさらに便利にする`createEntityAdapter`についての解説が主です。

セクションの最後で「チュートリアルはこれで全てです」とWebサイト上に記載されているため、Reduxの想定ではここまで使用するのがベターなのではないでしょうか。
正直、ツールにかなり依存してしまっているので、このセクションで使用する技術を使うとRedux Tool Kitにかなり合わせる必要があると思います。

[Part7](https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics),[Part8](https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced)
RTK Queryという通信部分に関するライブラリの説明です。
正直かなり難しいです。理解するにはRTK Query自体の知識も必要になると思います。
チュートリアル内でもおまけのような扱いとなっているので、この2つのセクションをやるかどうかは好みになってきます。

各セクションごとに私の所感を述べました。
もし、Reduxのチュートリアルに手をつけてみたいという方はこちらの所感を参考にしていただけると幸いです。

※チュートリアルを進める際の注意点として、Node.jsのバージョンが16以前でないとエラーが発生するのでそこだけ注意してください。

## Sliceの作成
現在のReduxではRedux Tool Kitの利用が主流となっています。
その中のAPIである`createSlice`について解説します。
まずは以下のコードをご覧ください。

```js
import { createSlice } from '@reduxjs/toolkit'

const initialState = [
  { id: '1', title: 'First Post!', content: 'Hello!' },
  { id: '2', title: 'Second Post', content: 'More text' },
]

const postsSlice = createSlice({
  name: 'posts'
  initialState,
  reducers: {
    postAdded(state, action) {
      state.push(action.payload)
    },
  },
});

export const { postAdded } = postsSlice.actions

export const selectAllPosts = (state) => state.posts
export const selectPostById = (state, userId) => state.posts.find((post) => post.id === userId)

export default postsSlice.reducer
```

上記のコードはSNSの投稿に関する`Slice`と考えてください。
`createSlice`はReduxで使用するために使われるAction creatorやReducerなどを作成してくれる便利な関数です。
ここでは以下の項目が必須になります。

- name
- initialState
- reducers

これらを定義した`postsSlice`を通して`postsSlice.actions`でアクションクリエーターを、`postsSlice.reducer`でリデューサーを取得できます。

## 作成したSliceの登録

Sliceを作成したらStoreに登録しStateを取り出せるように設定する必要があります。
以下のコードのようになります。

```js
import { configureStore } from '@reduxjs/toolkit'

import postsReducer from '../features/posts/postsSlice'

export default configureStore({
  reducer: {
    posts: postsReducer,
  },
})
```

`configureStore`関数の`reducer`オブジェクトにKey,Value形式で登録します。
こちらで登録したKeyの値を使うことで、後々に出てくる`useSelector`などでStoreから特定のStateを取得できます。

## コンポーネントでの使用

Storeからデータを取得する場合、`useSelector`関数を使用してデータを取得します。
Sliceで定義した`selectAllPosts`を引数として与えると、それに応じたStateの結果が返ってきます。

```js
import React from 'react'
import { useSelector } from 'react-redux'

import { selectAllPosts } from './features/posts/postsSlice'

export const PostsList = () => {
  const posts = useSelector(selectAllPosts)

  const renderedPosts = posts.map((post) => (
    <article className="post-excerpt" key={post.id}>
      <h3>{post.title}</h3>
      <p className="post-content">{post.content.substring(0, 100)}</p>
    </article>
  ))

  return (
    <section className="posts-list">
      <h2>Posts</h2>
      {renderedPosts}
    </section>
  )
}
```

また、`createSlice`で定義したアクションを実行する場合は、`useDispatch`関数を使用します。
`const dispatch = useDispatch()`のようにディスパッチ関数を取得し、ディスパッチ関数に`createSlice`で定義したアクションクリエータと引数を渡すことで実行することができます。
今回の場合は`Store`に新しい項目の追加を行っています。

```js
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { nanoid } from '@reduxjs/toolkit'

import { postAdded } from './postsSlice'

export const AddPostForm = () => {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')

  const dispatch = useDispatch()

  const onTitleChanged = (e) => setTitle(e.target.value)
  const onContentChanged = (e) => setContent(e.target.value)

  const onSavePostClicked = () => {
    if (title && content) {
      dispatch(
        postAdded({
          id: nanoid(),
          title,
          content,
        })
      )

      setTitle('')
      setContent('')
    }
  }

  return (
    <section>
      <h2>Add a New Post</h2>
      <form>
        <label htmlFor="postTitle">Post Title:</label>
        <input
          type="text"
          id="postTitle"
          name="postTitle"
          value={title}
          onChange={onTitleChanged}
        />
        <label htmlFor="postContent">Content:</label>
        <textarea
          id="postContent"
          name="postContent"
          value={content}
          onChange={onContentChanged}
        />
        <button type="button" onClick={onSavePostClicked}>
          Save Post
        </button>
      </form>
    </section>
  )
}
```

## まとめ

基本的な使い方はここまで見てきた方法の通りになります。
Reduxを使用する上で、その背後の概念を理解することがとても重要だと感じました。

一見、使用する変数などが散見していたり、様々な関数を使用したりと複雑に思えますが、内部でどのような動きを行なっているのかを理解すると挙動の理解も早まると思います。
この部分に関してはチュートリアルの1〜3章をじっくり読みながら、実際に手を動かして学ぶことをおすすめします。

Reduxの公式チュートリアルにはまだまだ便利な機能や非同期でのStateの更新など見どころがあるのでぜひ目を通してみてください。

また、今回私が実践したチュートリアルについては以下のリポジトリを参照してください。

https://github.com/takubii/redux-essentials-example-app

## 終わりに

今回は最近学んだReduxの簡単な使い方について紹介しました。
Stateの管理には様々なライブラリがありますのでアプリによって適したものを選ぶのが一番だと思いますが、候補として最初に上がりそうなのがReduxだと思います。
この記事が少しでもReduxの理解の助けになれば嬉しいです。
今回はこの辺りで締めたいと思います。
また機会があればお会いしましょう。