Reduxと反応して状態を管理する方法
著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Redux は、JavaScriptおよびReactアプリケーションで人気のあるデータストアです。 データバインディングは一方向に流れ、信頼できる唯一の情報源として保存する必要があるという中心的な原則に従います。 Reduxは、設計コンセプトが単純で、実装が比較的小さいために人気を博しました。
Reduxはいくつかの概念に従って動作します。 まず、 store は、データを選択するたびにフィールドを持つ単一のオブジェクトです。 データを更新するには、データの変更方法を示すアクションをディスパッチします。 次に、アクションを解釈し、reducersを使用してデータを更新します。 レデューサーは、以前の状態を変更する代わりに、データにアクションを適用し、新しい状態を返す関数です。
小さなアプリケーションでは、グローバルデータストアは必要ない場合があります。 ローカル状態とコンテキストを組み合わせて状態を管理できます。 ただし、アプリケーションの規模が大きくなると、ルートやコンポーネント間で情報が保持されるように、情報を一元的に保存することが有益な状況に遭遇する可能性があります。 そのような状況では、Reduxは組織化された方法でデータを保存および取得するための標準的な方法を提供します。
このチュートリアルでは、バードウォッチングテストアプリケーションを構築して、ReactアプリケーションでReduxを使用します。 ユーザーは、見た鳥を追加したり、もう一度見るたびに鳥を増やしたりすることができます。 単一のデータストアを構築し、ストアを更新するためのアクションとレデューサーを作成します。 次に、データをコンポーネントにプルし、新しい変更をディスパッチしてデータを更新します。
前提条件
- Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.22.0およびnpmバージョン6.14.6でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
- Create React App でセットアップされたReact開発環境で、不要なボイラープレートが削除されています。 これを設定するには、ステップ1 —Reactクラスコンポーネントの状態を管理する方法のチュートリアルの空のプロジェクトを作成します。 このチュートリアルでは、プロジェクト名として
redux-tutorial
を使用します。 - このチュートリアルでは、
useState
フックやカスタムフックなど、Reactコンポーネント、フック、フォームを使用します。 コンポーネントとフックについては、チュートリアルReactコンポーネントのフックで状態を管理する方法とReactでフォームを作成する方法で学ぶことができます。 - また、JavaScript、HTML、およびCSSの基本的な知識も必要です。これは、HTMLシリーズを使用してWebサイトを構築する方法、CSSシリーズを使用してWebサイトを構築する方法にあります。 、およびJavaScriptでコーディングする方法。
ステップ1—ストアを設定する
このステップでは、Reduxをインストールし、ルートコンポーネントに接続します。 次に、ベースストアを作成し、コンポーネントに情報を表示します。 このステップを完了すると、コンポーネントに情報が表示されたReduxの動作インスタンスができあがります。
開始するには、redux
とreact-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
というフィールドを持つオブジェクトを返します。 各鳥にはname
とviews
のカウントがあります。 関数の出力をstore
という値に保存し、store
をProvider
のstore
という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
)は常に最新の状態になります。
これでデータができたので、順序付けされていないリストに表示できます。 wrapper
のclassName
で周囲の<div>
を作成します。 内部に、<ul>
要素を追加し、 map()を使用してbirds
配列をループし、それぞれに新しい<li>
アイテムを返します。 必ずbird.name
をkey
として使用してください。
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>
で囲み、submit
のtype
を追加ボタンに追加して、すべてにアクセスできるようにしてください。
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
padding
をwrapper
クラスに追加します。 次に、鳥の名前を保持する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; }
さらに、各ボタンにpointer
のcursor
を指定します。これにより、ボタンにカーソルを合わせるとカーソルが変わり、ボタンがクリック可能であることをユーザーに示します。
ファイルを保存して閉じます。 これを行うと、ブラウザはコンポーネントで更新されます。
ボタンとフォームはまだアクションに接続されていないため、Reduxストアと対話することはできません。 手順2でアクションを追加し、手順3でそれらを接続します。
このステップでは、Reduxをインストールし、アプリケーション用の新しいストアを作成しました。 Provider
を使用してストアをアプリケーションに接続し、useSelector
フックを使用してコンポーネント内の要素にアクセスしました。
次のステップでは、新しい情報でストアを更新するためのアクションとレデューサーを作成します。
ステップ2—アクションとレデューサーを作成する
次に、鳥を追加してビューをインクリメントするアクションを作成します。 次に、アクションタイプに応じて情報を更新するレデューサーを作成します。 最後に、レデューサーを使用して、combineReducers
を使用してデフォルトストアを作成します。
アクションは、意図した変更を加えてデータストアに送信するメッセージです。 レデューサーはこれらのメッセージを受け取り、アクションタイプに応じて変更を適用することにより、共有ストアを更新します。 コンポーネントはストアで使用するアクションを送信し、レデューサーはアクションを使用してストア内のデータを更新します。 レデューサーを直接呼び出すことはありません。1つのアクションが複数のレデューサーに影響を与える場合があります。
アクションとレデューサーの整理にはさまざまなオプションがあります。 このチュートリアルでは、ドメインごとに整理します。 つまり、アクションとレデューサーは、影響を与える機能のタイプによって定義されます。
store
というディレクトリを作成します。
mkdir src/store
このディレクトリには、すべてのアクションとレデューサーが含まれます。 一部のパターンはそれらをコンポーネントと一緒に保存しますが、ここでの利点は、ストア全体の形状を個別に参照できることです。 新しい開発者がプロジェクトに参加すると、ストアの構造を一目で読み取ることができます。
store
ディレクトリ内にbirds
というディレクトリを作成します。 これには、鳥のデータを更新するためのアクションとレデューサーが含まれます。
mkdir src/store/birds
次に、birds.js
というファイルを開いて、アクションとレデューサーの追加を開始できるようにします。 アクションとレデューサーが多数ある場合は、それらをbirds.actions.js
やbirds.reducers.js
などの個別のファイルに分割することをお勧めしますが、数が少ない場合は、次の場合に読みやすくなります。それらは同じ場所にあります:
nano src/store/birds/birds.js
まず、アクションを作成します。 アクションは、次のステップで使用するdispatch
というメソッドを使用してコンポーネントからストアに送信するメッセージです。
アクションは、type
フィールドを持つオブジェクトを返す必要があります。 それ以外の場合、returnオブジェクトには、送信する追加情報を含めることができます。
bird
を引数として取り、'ADD_BIRD'
のtype
とbird
を含むオブジェクトを返す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 } }
birds
とgear
の2つのレデューサーを作成します。 birds
レデューサーのstate
は、鳥の配列になります。 gear
レデューサーのstate
は、ギア情報を含むオブジェクトになります。
birds.js
内に、state
とaction
を取り、変更なしで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; } }
state
をdefault
値として返していることに注意してください。 さらに重要なのは、state
を直接変更していないことです。 代わりに、古い配列を拡散して新しい値を追加することにより、新しい配列を作成しています。
アクションが1つあるので、ビューをインクリメントするためのアクションを作成できます。
incrementBird
というアクションを作成します。 addBird
アクションと同様に、これは引数として鳥を取り、type
とbird
を持つオブジェクトを返します。 唯一の違いは、タイプが'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()を使用して、インクリメントする必要のある鳥を配列から引き出し、各name
をaction.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つのレデューサーができました。 最後のステップは、ストアを初期化できるようにレデューサーをエクスポートすることです。 最初のステップでは、オブジェクトを返す関数を渡してストアを作成しました。 この場合も同じことをします。 この関数は、store
とaction
を取得し、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.name
でincrementBird
を呼び出し、結果を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()
を呼び出して入力をクリアします。 最後に、handleSubmit
をform
のonSubmit
イベントハンドラーに渡します。
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シリーズのコーディング方法ページに戻ってください。