Yanonoblog!

こつこつと

Reduxのデータフロー 解説(2/2)

はじめに

Reduxの公式ドキュメントに沿って概要を押さえつつ、気になった部分を深掘って整理しています。

本記事では 、Reduxのサンプルアプリを通してデータフローについてまとめていきます。

前回の記事の続きになります!

アプリの構成

構成はこのようになっています。

前回から/features配下を追加しています。

├── /public             # HTMLホストページのテンプレートやアイコンなどの静的ファイル
│
├── /src
│        ├── index.js        # アプリケーションのエントリーポイント。<Provider>と<App>コンポーネントをレンダリングする
│        ├── App.js          # メインのアプリケーションコンポーネント。ナビゲーションバーをレンダリングし、ルーティングを処理する
│        └── index.css       # アプリケーション全体のスタイル
│
├── /src/api
│        ├── client.js       # GETとPOSTリクエストを可能にする小さなAJAXリクエストクライアント
│        └── server.js       # データのフェイクREST APIを提供する。アプリはこれらのフェイクエンドポイントからデータを取得する
│
├── /src/features/posts     # ****前回追加****
│        ├── postsSlice.js   # 投稿(post)に関するデータやリデューサーをカプセル化する
│        └── PostsList.js    # 投稿を一覧表示するコンポーネント
│
└── /src/app
         ├── Navbar.js       # 上部のヘッダーとナビゲーションコンテンツをレンダリングする
         └── store.js        # Reduxストアのインスタンスを作成する

https://codesandbox.io/s/github/reduxjs/redux-essentials-example-app/tree/master/?from-embed

上記のアプリをロードすると、ヘッダーとウェルカムメッセージが表示されるはずです。

投稿機能の追加

記事を書いて保存できる "Add New Post "フォームを作成します。

  1. 空のフォームを作り、ページに追加します。
  2. フォームをReduxストアに接続して、"Save Post "ボタンをクリックすると新しい投稿が追加されるようにします。
  3. フォームのUI(**features/posts/AddPostForm.js**)を作成します。 投稿タイトル用のテキスト入力と、投稿本文用のテキストエリアを追加します。
features/posts/AddPostForm.js
import React, { useState } from 'react'

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

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

  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">Save Post</button>
      </form>
    </section>
  )
}

useStateとonChangeを用いた一般的なフォームですね。

コンポーネントの流用 - フォーム

先程作成した投稿フォームをトップのコンポーネントに追加するとします。

App.js
<Route
  exact
  path="/"
  render={() => (
    <React.Fragment>
      <AddPostForm />
      <PostsList />
    </React.Fragment>
  )}
/>

投稿の保存

新しい投稿をReduxストアに追加するために、postsスライスを更新します。

postsスライスはpostsデータの更新を担当します。

今は空ですが、createSliceの中にはreducersというオブジェクトがあります。

投稿が追加された場合を処理するために、この中にreducer関数を追加する必要があります。

reducersの中に、postAddedリデューサー関数を追加します。

features/posts/postsSlice.js
const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    postAdded(state, action) {  // ーーーーーーーーーーーーーー追記
      state.push(action.payload)
    }
  }
})

export const { postAdded } = postsSlice.actions  // ーーー追記

export default postsSlice.reducer

postAdded関数は2つの引数(現在の状態の値と、ディスパッチされたアクションオブジェクト)を受け取ります。

postsスライスは自分が担当するデータしか知らないので、引数のstateはReduxのステートオブジェクト全体ではなく、postsの配列そのものになります。

アクションオブジェクトは新しい投稿をaction.payloadフィールドとして持ち、その新しい投稿オブジェクトをstate配列に入れます。

postAddedリデューサー関数を書くと、createSliceは自動的に同じ名前の "アクションクリエイター "関数を生成します。

このアクションクリエイターをエクスポートしてUIコンポーネントで使用することで、ユーザーが "Save Post "をクリックしたときにアクションをディスパッチすることができます。

保存処理

postAdded アクションをディスパッチし、ユーザーが書いたタイトルと内容を含む新しい post オブジェクトを渡すクリックハンドラをAddPostFormに追加する必要があります。

投稿オブジェクトにはidフィールドも必要です。

今回の例では、最初のテスト投稿ではIDにダミーの数字を使っていますので、Redux Toolkitのnanoid関数を用いてランダムなユニークIDを生成してIDに指定します。。

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>
        {/* omit form inputs */}
        <button type="button" onClick={onSavePostClicked}>
          Save Post
        </button>
      </form>
    </section>
  )
}

データフローまとめ

  1. 投稿リストはuseSelectorを使ってストアから投稿の初期セットを読み込み、初期UIをレンダリングする
  2. 新しい投稿エントリーのデータを含む postAdded アクションをディスパッチする
  3. posts reducerはpostAddedアクションを見て、新しいエントリでposts配列を更新する
  4. ReduxストアはUIにデータの変更を伝える
  5. 投稿リストは更新された投稿の配列を再レンダリングする

参考

続く…

コメント

本記事の内容は以上になります!

書籍の続きのアウトプットも随時更新したいと思います。


プログラミングスクールのご紹介 (卒業生より)

お世話になったプログラミングスクールであるRUNTEQです♪

https://runteq.jp/r/ohtFwbjW

こちらのリンクを経由すると1万円引きになります。

RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

https://twitter.com/outputky