Reduxと反応して状態を管理する方法

提供:Dev Guides
移動先:案内検索

著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Redux は、JavaScriptおよびReactアプリケーションで人気のあるデータストアです。 データバインディングは一方向に流れ、信頼できる唯一の情報源として保存する必要があるという中心的な原則に従います。 Reduxは、設計コンセプトが単純で、実装が比較的小さいために人気を博しました。

Reduxはいくつかの概念に従って動作します。 まず、 store は、データを選択するたびにフィールドを持つ単一のオブジェクトです。 データを更新するには、データの変更方法を示すアクションをディスパッチします。 次に、アクションを解釈し、reducersを使用してデータを更新します。 レデューサーは、以前の状態を変更する代わりに、データにアクションを適用し、新しい状態を返す関数です。

小さなアプリケーションでは、グローバルデータストアは必要ない場合があります。 ローカル状態コンテキストを組み合わせて状態を管理できます。 ただし、アプリケーションの規模が大きくなると、ルートやコンポーネント間で情報が保持されるように、情報を一元的に保存することが有益な状況に遭遇する可能性があります。 そのような状況では、Reduxは組織化された方法でデータを保存および取得するための標準的な方法を提供します。

このチュートリアルでは、バードウォッチングテストアプリケーションを構築して、ReactアプリケーションでReduxを使用します。 ユーザーは、見た鳥を追加したり、もう一度見るたびに鳥を増やしたりすることができます。 単一のデータストアを構築し、ストアを更新するためのアクションとレデューサーを作成します。 次に、データをコンポーネントにプルし、新しい変更をディスパッチしてデータを更新します。

前提条件

ステップ1—ストアを設定する

このステップでは、Reduxをインストールし、ルートコンポーネントに接続します。 次に、ベースストアを作成し、コンポーネントに情報を表示します。 このステップを完了すると、コンポーネントに情報が表示されたReduxの動作インスタンスができあがります。

開始するには、reduxreact-reduxをインストールします。 パッケージreduxはフレームワークに依存せず、アクションとレデューサーを接続します。 パッケージreact-reduxには、ReactプロジェクトでReduxストアを実行するためのバインディングが含まれています。 react-reduxのコードを使用して、コンポーネントからアクションを送信し、ストアからコンポーネントにデータをプルします。

npmを使用して、次のコマンドで2つのパッケージをインストールします

npm install --save redux react-redux

コンポーネントのインストールが完了すると、次のような出力が表示されます。 出力はわずかに異なる場合があります。

Output...
+ [email protected]
+ [email protected]
added 2 packages from 1 contributor, updated 1 package and audited 1639 packages in 20.573s

パッケージがインストールされたので、Reduxをプロジェクトに接続する必要があります。 Reduxを使用するには、ルートコンポーネントをProviderでラップして、ツリー内のすべての子コンポーネントでストアを使用できるようにする必要があります。 これは、Reactのネイティブコンテキストを使用してProviderを追加する方法と似ています。

src/index.jsを開きます:

nano src/index.js

Providerコンポーネントをreact-reduxパッケージからインポートします。 コードに次の強調表示された変更を加えて、他のコンポーネントの周囲のルートコンポーネントにProviderを追加します。

redux-tutorial / src / index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';

ReactDOM.render(
  <React.StrictMode>
    <Provider>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

コンポーネントをラップしたので、storeを追加します。 storeは、データの中心的なコレクションです。 次のステップでは、デフォルト値を設定してストアを更新するreducersの作成方法を学習しますが、今のところ、データをハードコーディングします。

createStore関数をreduxからインポートし、オブジェクトを返す関数を渡します。 この場合、個々の鳥の配列を指すbirdsというフィールドを持つオブジェクトを返します。 各鳥にはnameviewsのカウントがあります。 関数の出力をstoreという値に保存し、storeProviderstoreというpropに渡します。 :

redux-tutorial / src / index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore } from 'redux';

const store = createStore(() => ({
  birds: [
    {
      name: 'robin',
      views: 1
    }
  ]
}));

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

ファイルを保存して閉じます。 いくつかのデータができたので、それを表示できるようにする必要があります。 src/components/App/App.jsを開きます:

nano src/components/App/App.js

contextと同様に、すべての子コンポーネントは、追加の小道具なしでストアにアクセスできます。 Reduxストアのアイテムにアクセスするには、react-reduxパッケージのuseSelectorというフックを使用します。 useSelectorフックは、セレクター関数を引数として取ります。 セレクター関数は、必要なフィールドを返すために使用する引数としてストアの状態を受け取ります。

redux-tutorial / src / components / App / App.js

import React from 'react';
import { useSelector } from 'react-redux';
import './App.css';

function App() {
  const birds = useSelector(state => state.birds);

  return <></>
}

export default App;

useSelectorはカスタムフックであるため、フックが呼び出されるたびにコンポーネントが再レンダリングされます。 つまり、データ(birds)は常に最新の状態になります。

これでデータができたので、順序付けされていないリストに表示できます。 wrapperclassNameで周囲の<div>を作成します。 内部に、<ul>要素を追加し、 map()を使用してbirds配列をループし、それぞれに新しい<li>アイテムを返します。 必ずbird.namekeyとして使用してください。

redux-tutorial / src / components / App / App.js

import React from 'react';
import { useSelector } from 'react-redux'
import './App.css';

function App() {
  const birds = useSelector(state => state.birds);

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <ul>
        {birds.map(bird => (
          <li key={bird.name}>
            <h3>{bird.name}</h3>
            <div>
              Views: {bird.views}
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

ファイルを保存します。 ファイルが保存されると、ブラウザがリロードされ、鳥のリストが見つかります::

基本的なリストができたので、バードウォッチングアプリに必要な残りのコンポーネントを追加します。 まず、ビューのリストの後にビューをインクリメントするボタンを追加します。

redux-tutorial / src / components / App / App.js

import React from 'react';
import { useSelector } from 'react-redux'
import './App.css';

function App() {
  const birds = useSelector(state => state.birds);

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <ul>
        {birds.map(bird => (
          <li key={bird.name}>
            <h3>{bird.name}</h3>
            <div>
              Views: {bird.views}
              <button><span role="img" aria-label="add">➕</span></button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

次に、鳥のリストの前に単一の<input>を含む<form>を作成して、ユーザーが新しい鳥を追加できるようにします。 <input><label>で囲み、submittypeを追加ボタンに追加して、すべてにアクセスできるようにしてください。

redux-tutorial / src / components / App / App.js

import React from 'react';
import { useSelector } from 'react-redux'
import './App.css';

function App() {
  const birds = useSelector(state => state.birds);

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <form>
        <label>
          <p>
            Add Bird
          </p>
          <input type="text" />
        </label>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <ul>
        {birds.map(bird => (
          <li key={bird.name}>
            <h3>{bird.name}</h3>
            <div>
              Views: {bird.views}
              <button><span role="img" aria-label="add">➕</span></button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

ファイルを保存して閉じます。 次に、App.cssを開いて、スタイルを追加します。

nano src/components/App/App.css

paddingwrapperクラスに追加します。 次に、鳥の名前を保持するh3要素を大文字にします。 最後に、ボタンのスタイルを設定します。 追加<button>のデフォルトのボタンスタイルを削除してから、フォーム<button>にマージンを追加します。

ファイルの内容を次のように置き換えます。

redux-tutorial / src / components / App / App.css

.wrapper {
    padding: 20px;
}

.wrapper h3 {
    text-transform: capitalize;
}

.wrapper form button {
    margin: 10px 0;
    cursor: pointer;
}

.wrapper ul button {
    background: none;
    border: none;
    cursor: pointer;
}

さらに、各ボタンにpointercursorを指定します。これにより、ボタンにカーソルを合わせるとカーソルが変わり、ボタンがクリック可能であることをユーザーに示します。

ファイルを保存して閉じます。 これを行うと、ブラウザはコンポーネントで更新されます。

ボタンとフォームはまだアクションに接続されていないため、Reduxストアと対話することはできません。 手順2でアクションを追加し、手順3でそれらを接続します。

このステップでは、Reduxをインストールし、アプリケーション用の新しいストアを作成しました。 Providerを使用してストアをアプリケーションに接続し、useSelectorフックを使用してコンポーネント内の要素にアクセスしました。

次のステップでは、新しい情報でストアを更新するためのアクションとレデューサーを作成します。

ステップ2—アクションとレデューサーを作成する

次に、鳥を追加してビューをインクリメントするアクションを作成します。 次に、アクションタイプに応じて情報を更新するレデューサーを作成します。 最後に、レデューサーを使用して、combineReducersを使用してデフォルトストアを作成します。

アクションは、意図した変更を加えてデータストアに送信するメッセージです。 レデューサーはこれらのメッセージを受け取り、アクションタイプに応じて変更を適用することにより、共有ストアを更新します。 コンポーネントはストアで使用するアクションを送信し、レデューサーはアクションを使用してストア内のデータを更新します。 レデューサーを直接呼び出すことはありません。1つのアクションが複数のレデューサーに影響を与える場合があります。

アクションとレデューサーの整理にはさまざまなオプションがあります。 このチュートリアルでは、ドメインごとに整理します。 つまり、アクションとレデューサーは、影響を与える機能のタイプによって定義されます。

storeというディレクトリを作成します。

mkdir src/store

このディレクトリには、すべてのアクションとレデューサーが含まれます。 一部のパターンはそれらをコンポーネントと一緒に保存しますが、ここでの利点は、ストア全体の形状を個別に参照できることです。 新しい開発者がプロジェクトに参加すると、ストアの構造を一目で読み取ることができます。

storeディレクトリ内にbirdsというディレクトリを作成します。 これには、鳥のデータを更新するためのアクションとレデューサーが含まれます。

mkdir src/store/birds

次に、birds.jsというファイルを開いて、アクションとレデューサーの追加を開始できるようにします。 アクションとレデューサーが多数ある場合は、それらをbirds.actions.jsbirds.reducers.jsなどの個別のファイルに分割することをお勧めしますが、数が少ない場合は、次の場合に読みやすくなります。それらは同じ場所にあります:

nano src/store/birds/birds.js

まず、アクションを作成します。 アクションは、次のステップで使用するdispatchというメソッドを使用してコンポーネントからストアに送信するメッセージです。

アクションは、typeフィールドを持つオブジェクトを返す必要があります。 それ以外の場合、returnオブジェクトには、送信する追加情報を含めることができます。

birdを引数として取り、'ADD_BIRD'typebirdを含むオブジェクトを返すaddBirdsという関数を作成します。フィールド:

redux-tutorial / src / store / birds / birds.js

export function addBird(bird) {
  return {
    type: 'ADD_BIRD',
    bird,
  }
}

後でコンポーネントからインポートしてディスパッチできるように、関数をエクスポートしていることに注意してください。

typeフィールドはレデューサーとの通信に重要であるため、慣例により、ほとんどのReduxストアは、スペルミスから保護するためにタイプを変数に保存します。

文字列'ADD_BIRD'を保存するADD_BIRDという名前のconstを作成します。 次に、アクションを更新します。

redux-tutorial / src / store / birds / birds.js

const ADD_BIRD = 'ADD_BIRD';

export function addBird(bird) {
  return {
    type: ADD_BIRD,
    bird,
  }
}

アクションができたので、アクションに応答するレデューサーを作成します。

レデューサーは、アクションに基づいて状態がどのように変化するかを決定する機能です。 アクション自体は変更を加えません。 レデューサーは状態を取得し、アクションに基づいて変更を加えます。

レデューサーは、現在の状態とアクションの2つの引数を受け取ります。 現在の状態とは、ストアの特定のセクションの状態を指します。 通常、レデューサーの名前はストアのフィールドと一致します。 たとえば、次のような形のストアがあるとします。

{
  birds: [
    // collection of bird objects
  ],
  gear: {
    // gear information
  }
}

birdsgearの2つのレデューサーを作成します。 birdsレデューサーのstateは、鳥の配列になります。 gearレデューサーのstateは、ギア情報を含むオブジェクトになります。

birds.js内に、stateactionを取り、変更なしでstateを返すbirdsというレデューサーを作成します。

redux-tutorial / src / store / birds / birds.js

const ADD_BIRD = 'ADD_BIRD';

export function addBird(bird) {
  return {
    type: ADD_BIRD,
    bird,
  }
}

function birds(state, action) {
  return state;
}

レデューサーをエクスポートしていないことに注意してください。 レデューサーを直接使用するのではなく、それらを組み合わせて使用可能なコレクションにし、エクスポートしてindex.jsにベースストアを作成するために使用します。 変更がない場合は、stateを返す必要があることにも注意してください。 Reduxは、アクションをディスパッチするたびにすべてのレデューサーを実行するため、状態を返さない場合は、変更を失うリスクがあります。

最後に、Reduxは変更がない場合に状態を返すため、デフォルトパラメーターを使用してデフォルト状態を追加します。

プレースホルダーの鳥の情報を含むdefaultBirds配列を作成します。 次に、stateを更新して、デフォルトのパラメーターとしてdefaultBirdsを含めます。

redux-tutorial / src / store / birds / birds

const ADD_BIRD = 'ADD_BIRD';

export function addBird(bird) {
  return {
    type: ADD_BIRD,
    bird,
  }
}

const defaultBirds = [
  {
    name: 'robin',
    views: 1,
  }
];

function birds(state=defaultBirds, action) {
  return state;
}

状態を返すレデューサーができたので、アクションを使用して変更を適用できます。 最も一般的なパターンは、action.typeスイッチを使用して変更を適用することです。

action.typeを調べるswitchステートメントを作成します。 ケースがADD_BIRDの場合、 Spread は現在の状態を新しい配列に出力し、単一のビューで鳥を追加します。

redux-tutorial / src / store / birds / birds.js

const ADD_BIRD = 'ADD_BIRD';

export function addBird(bird) {
  return {
    type: ADD_BIRD,
    bird,
  }
}

const defaultBirds = [
  {
    name: 'robin',
    views: 1,
  }
];

function birds(state=defaultBirds, action) {
  switch (action.type) {
    case ADD_BIRD:
      return [
        ...state,
        {
          name: action.bird,
          views: 1
        }
      ];
    default:
      return state;
  }
}

statedefault値として返していることに注意してください。 さらに重要なのは、stateを直接変更していないことです。 代わりに、古い配列を拡散して新しい値を追加することにより、新しい配列を作成しています。

アクションが1つあるので、ビューをインクリメントするためのアクションを作成できます。

incrementBirdというアクションを作成します。 addBirdアクションと同様に、これは引数として鳥を取り、typebirdを持つオブジェクトを返します。 唯一の違いは、タイプが'INCREMENT_BIRD'になることです。

redux-tutorial / src / store / birds / birds.js

const ADD_BIRD = 'ADD_BIRD';
const INCREMENT_BIRD = 'INCREMENT_BIRD';

export function addBird(bird) {
  return {
    type: ADD_BIRD,
    bird,
  }
}

export function incrementBird(bird) {
  return {
    type: INCREMENT_BIRD,
    bird
  }
}

const defaultBirds = [
  {
    name: 'robin',
    views: 1,
  }
];

function birds(state=defaultBirds, action) {
  switch (action.type) {
    case ADD_BIRD:
      return [
        ...state,
        {
          name: action.bird,
          views: 1
        }
      ];
    default:
      return state;
  }
}

このアクションは別ですが、同じレデューサーを使用します。 アクションはデータに加えたい変更を伝え、レデューサーはそれらの変更を適用して新しい状態を返すことを忘れないでください。

鳥を増やすには、新しい鳥を追加するだけでは不十分です。 birdsの中に、INCREMENT_BIRDの新しいケースを追加します。 次に、 find()を使用して、インクリメントする必要のある鳥を配列から引き出し、各nameaction.birdと比較します。

redux-tutorial / src / store / bird / birds.js

const ADD_BIRD = 'ADD_BIRD';
...
function birds(state=defaultBirds, action) {
  switch (action.type) {
    case ADD_BIRD:
      return [
        ...state,
        {
          name: action.bird,
          views: 1
        }
      ];
    case INCREMENT_BIRD:
      const bird = state.find(b => action.bird === b.name);
      return state;
    default:
      return state;
  }
}

変更する必要のある鳥がありますが、変更されていないすべての鳥と更新中の鳥を含む新しい状態を返す必要があります。 action.nameと等しくないnameを持つすべての鳥を選択して、state.filterを持つ残りのすべての鳥を選択します。 次に、birds配列を拡散し、最後にbirdを追加して、新しい配列を返します。

redux-tutorial / src / store / bird / birds.js

const ADD_BIRD = 'ADD_BIRD';
...

function birds(state=defaultBirds, action) {
  switch (action.type) {
    case ADD_BIRD:
      return [
        ...state,
        {
          name: action.bird,
          views: 1
        }
      ];
    case INCREMENT_BIRD:
      const bird = state.find(b => action.bird === b.name);
      const birds = state.filter(b => action.bird !== b.name);
      return [
        ...birds,
        bird,
      ];
    default:
      return state;
  }
}

最後に、viewをインクリメントして新しいオブジェクトを作成し、birdを更新します。

redux-tutorial / src / store / bird / birds.js

const ADD_BIRD = 'ADD_BIRD';
...
function birds(state=defaultBirds, action) {
  switch (action.type) {
    case ADD_BIRD:
      return [
        ...state,
        {
          name: action.bird,
          views: 1
        }
      ];
    case INCREMENT_BIRD:
      const bird = state.find(b => action.bird === b.name);
      const birds = state.filter(b => action.bird !== b.name);
      return [
        ...birds,
        {
          ...bird,
          views: bird.views + 1
        }
      ];
    default:
      return state;
  }
}

データの並べ替えにレデューサーを使用していないことに注意してください。 ビューにはユーザーに情報が表示されるため、並べ替えはビューの懸念事項と見なすことができます。 名前で並べ替えるビューとビュー数で並べ替えるビューを1つずつ作成できるため、個々のコンポーネントに並べ替えを処理させることをお勧めします。 代わりに、レデューサーはデータの更新に焦点を合わせ、コンポーネントはデータをユーザーが使用できるビューに変換することに焦点を合わせます。

同じ名前の鳥を追加できるため、このレデューサーも不完全です。 本番アプリでは、鳥を追加する前に検証するか、鳥に一意のidを指定して、nameではなくidで鳥を選択できるようにする必要があります。

これで、2つの完全なアクションと1つのレデューサーができました。 最後のステップは、ストアを初期化できるようにレデューサーをエクスポートすることです。 最初のステップでは、オブジェクトを返す関数を渡してストアを作成しました。 この場合も同じことをします。 この関数は、storeactionを取得し、storeの特定のスライスをアクションとともにレデューサーに渡します。 次のようになります。

export function birdApp(store={}, action) {
    return {
        birds: birds(store.birds, action)
    }
}

物事を単純化するために、ReduxにはcombineReducersと呼ばれるヘルパー関数があります。これはレデューサーを組み合わせたものです。

birds.js内で、reduxからcombineReducersをインポートします。 次に、birdsを使用して関数を呼び出し、結果をエクスポートします。

redux-tutorial / src / store / bird / birds.js

import { combineReducers } from 'redux';
const ADD_BIRD = 'ADD_BIRD';
const INCREMENT_BIRD = 'INCREMENT_BIRD';

export function addBird(bird) {
  return {
    type: ADD_BIRD,
    bird,
  }
}

export function incrementBird(bird) {
  return {
    type: INCREMENT_BIRD,
    bird
  }
}

const defaultBirds = [
  {
    name: 'robin',
    views: 1,
  }
];

function birds(state=defaultBirds, action) {
  switch (action.type) {
    case ADD_BIRD:
      return [
        ...state,
        {
          name: action.bird,
          views: 1
        }
      ];
    case INCREMENT_BIRD:
      const bird = state.find(b => action.bird === b.name);
      const birds = state.filter(b => action.bird !== b.name);
      return [
        ...birds,
        {
          ...bird,
          views: bird.views + 1
        }
      ];
    default:
      return state;
  }
}

const birdApp = combineReducers({
  birds
});

export default birdApp;

ファイルを保存して閉じます。

アクションとレデューサーがすべて設定されています。 最後のステップは、プレースホルダー関数の代わりに結合されたレデューサーを使用してストアを初期化することです。

src/index.jsを開きます:

nano src/index.js

birds.jsからbirdAppをインポートします。 次に、birdAppを使用してstoreを初期化します。

redux-tutorial / src / index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import birdApp from './store/birds/birds';

const store = createStore(birdApp);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

ファイルを保存して閉じます。 これを行うと、ブラウザはアプリケーションで更新されます。

このステップでは、アクションとレデューサーを作成しました。 typeを返すアクションを作成する方法と、アクションを使用してアクションに基づいて新しい状態を構築して返すレデューサーを構築する方法を学習しました。 最後に、レデューサーを組み合わせて、ストアの初期化に使用した関数にしました。

これで、Reduxストアがすべてセットアップされ、変更の準備が整いました。 次のステップでは、コンポーネントからアクションをディスパッチしてデータを更新します。

ステップ3—コンポーネントの変更をディスパッチする

このステップでは、コンポーネントからアクションをインポートして呼び出します。 dispatchというメソッドを使用してアクションを送信し、formおよびbuttonイベントハンドラー内にアクションをディスパッチします。 。

このステップを完了すると、Reduxストアとカスタムコンポーネントを組み合わせた実用的なアプリケーションができあがります。 Reduxストアをリアルタイムで更新し、コンポーネントの変更に応じて情報を表示できるようになります。

作業アクションができたので、ストアを更新できるように、それらをイベントに接続する必要があります。 使用するメソッドはdispatchと呼ばれ、特定のアクションをReduxストアに送信します。 Reduxは、ディスパッチしたアクションを受信すると、そのアクションをレデューサーに渡し、レデューサーがデータを更新します。

App.jsを開きます:

nano src/components/App/App.js

App.js内で、react-reduxからフックuseDispathをインポートします。 次に、関数を呼び出して、新しいdispatch関数を作成します。

redux-tutorial / src / components / App / App.js

import React from 'react';
import { useDispatch, useSelector } from 'react-redux'
import './App.css';

function App() {
  ...
}

export default App;

次に、アクションをインポートする必要があります。 アクションはオブジェクトを返す関数であることを忘れないでください。 オブジェクトは、最終的にdispatch関数に渡すものです。

ストアからincrementBirdをインポートします。 次に、ボタンにonClickイベントを作成します。 ユーザーがボタンをクリックしたら、bird.nameincrementBirdを呼び出し、結果をdispatchに渡します。 読みやすくするには、dispatch内でincrementBird関数を呼び出します。

redux-tutorial / src / components / App / App.js

import React from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { incrementBird } from '../../store/birds/birds';
import './App.css';

function App() {
  const birds = useSelector(state => state.birds);
  const dispatch = useDispatch();

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <form>
        <label>
          <p>
            Add Bird
          </p>
          <input type="text" />
        </label>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <ul>
        {birds.map(bird => (
          <li key={bird.name}>
            <h3>{bird.name}</h3>
            <div>
              Views: {bird.views}
              <button onClick={() => dispatch(incrementBird(bird.name))}><span role="img" aria-label="add">➕</span></button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

ファイルを保存します。 そうすると、ロビンカウントを増やすことができます。

次に、addBirdアクションをディスパッチする必要があります。 これには2つのステップがあります。入力を内部状態に保存することと、onSubmitでディスパッチをトリガーすることです。

useStateフックを使用して入力値を保存します。 入力にvalueを設定して、入力を制御コンポーネントに変換してください。 制御されたコンポーネントの詳細については、チュートリアルReactでフォームを作成する方法を確認してください。

コードに次の変更を加えます。

redux-tutorial / src / components / App / App.js

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { incrementBird } from '../../store/birds/birds';
import './App.css';

function App() {
  const [birdName, setBird] = useState('');
  const birds = useSelector(state => state.birds);
  const dispatch = useDispatch();

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <form>
        <label>
          <p>
            Add Bird
          </p>
          <input
            type="text"
            onChange={e => setBird(e.target.value)}
            value={birdName}
          />
        </label>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <ul>
        ...
      </ul>
    </div>
  );
}

export default App;

次に、birds.jsからaddBirdをインポートし、handleSubmitという関数を作成します。 handleSubmit関数内で、event.preventDefaultを使用してページフォームを送信しないようにしてから、birdNameを引数としてaddBirdアクションをディスパッチします。 アクションをディスパッチした後、setBird()を呼び出して入力をクリアします。 最後に、handleSubmitformonSubmitイベントハンドラーに渡します。

redux-tutorial / src / components / App / App.js

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { addBird, incrementBird } from '../../store/birds/birds';
import './App.css';

function App() {
  const [birdName, setBird] = useState('');
  const birds = useSelector(state => state.birds);
  const dispatch = useDispatch();

  const handleSubmit = event => {
    event.preventDefault();
    dispatch(addBird(birdName))
    setBird('');
  };

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <form onSubmit={handleSubmit}>
        <label>
          <p>
            Add Bird
          </p>
          <input
            type="text"
            onChange={e => setBird(e.target.value)}
            value={birdName}
          />
        </label>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <ul>
        {birds.map(bird => (
          <li key={bird.name}>
            <h3>{bird.name}</h3>
            <div>
              Views: {bird.views}
              <button onClick={() => dispatch(incrementBird(bird.name))}><span role="img" aria-label="add">➕</span></button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

ファイルを保存します。 これを行うと、ブラウザがリロードされ、鳥を追加できるようになります。

これで、アクションを呼び出して、ストアの鳥のリストを更新しています。 アプリケーションを更新すると、以前の情報が失われることに注意してください。 ストアはすべてメモリに含まれているため、ページを更新するとデータが消去されます。

このリストの順序は、リストの上位にある鳥をインクリメントした場合にも変更されます。

手順2で見たように、レデューサーはデータの並べ替えには関与しません。 コンポーネントの予期しない変更を防ぐために、コンポーネント内のデータを並べ替えることができます。 sort()関数をbirds配列に追加します。 並べ替えは配列を変更するため、ストアを変更する必要はありません。 並べ替える前に、データを拡散して新しい配列を作成してください。

redux-tutorial / src / components / App / App.js

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { addBird, incrementBird } from '../../store/birds/birds';
import './App.css';

function App() {
  const [birdName, setBird] = useState('');
  const birds = [...useSelector(state => state.birds)].sort((a, b) => {
    return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
  });
  const dispatch = useDispatch();

  const handleSubmit = event => {
    event.preventDefault();
    dispatch(addBird(birdName))
    setBird('');
  };

  return (
    <div className="wrapper">
      <h1>Bird List</h1>
      <form onSubmit={handleSubmit}>
        <label>
          <p>
            Add Bird
          </p>
          <input
            type="text"
            onChange={e => setBird(e.target.value)}
            value={birdName}
          />
        </label>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <ul>
        {birds.map(bird => (
          <li key={bird.name}>
            <h3>{bird.name}</h3>
            <div>
              Views: {bird.views}
              <button onClick={() => dispatch(incrementBird(bird.name))}><span role="img" aria-label="add">➕</span></button>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

ファイルを保存します。 これを行うと、鳥を増やすときにコンポーネントがアルファベット順に残ります。

Reduxストアでやりすぎないようにすることが重要です。 レデューサーを最新の情報の維持に集中させてから、コンポーネント内のユーザーのデータをプルして操作します。

注:このチュートリアルでは、各アクションとレデューサーにかなりの量のコードがあることに注意してください。 幸い、ボイラープレートコードの量を減らすのに役立つ ReduxToolkitと呼ばれる公式にサポートされているプロジェクトがあります。 Redux Toolkitは、アクションとリデューサーをすばやく作成するための独自のユーティリティセットを提供し、より少ないコードでストアを作成および構成できるようにします。


このステップでは、コンポーネントからアクションをディスパッチしました。 アクションを呼び出す方法と結果をディスパッチ関数に送信する方法を学び、それらをコンポーネントのイベントハンドラーに接続して、完全にインタラクティブなストアを作成しました。 最後に、ストアを直接変更せずにデータを並べ替えることで、一貫したユーザーエクスペリエンスを維持する方法を学びました。

結論

Reduxは人気のあるシングルストアです。 共通の情報源を必要とするコンポーネントを操作する場合に有利です。 ただし、すべてのプロジェクトで常に正しい選択であるとは限りません。 小規模なプロジェクトまたは分離されたコンポーネントを持つプロジェクトは、組み込みの状態管理とコンテキストを使用できます。 ただし、アプリケーションの複雑さが増すにつれて、データの整合性を維持するために中央ストレージが重要になる場合があります。 このような場合、Reduxは、最小限の労力でコンポーネント全体で使用できる単一の統合データストアを作成するための優れたツールです。

Reactチュートリアルをもっと読みたい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。