Reactコンポーネントのフックで状態を管理する方法
著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
React 開発では、アプリケーションデータが時間の経過とともにどのように変化するかを追跡することを、状態管理と呼びます。 アプリケーションの状態を管理することで、ユーザーの入力に応答する動的なアプリを作成できるようになります。 クラスベースの状態管理やReduxなどのサードパーティライブラリなど、Reactで状態を管理する方法はたくさんあります。 このチュートリアルでは、公式のReactドキュメント:フックで推奨されている方法を使用して、機能コンポーネントの状態を管理します。
フックは、コンポーネントのpropsが変更されたときにカスタム関数を実行する幅広いツールセットです。 この状態管理の方法ではクラスを使用する必要がないため、開発者はフックを使用して、共有と保守が容易な、より短く、より読みやすいコードを記述できます。 フックとクラスベースの状態管理の主な違いの1つは、すべての状態を保持する単一のオブジェクトがないことです。 代わりに、状態を複数の部分に分割して、個別に更新することができます。
このチュートリアル全体を通して、useStateおよびuseReducerフックを使用して状態を設定する方法を学習します。 useStateフックは、現在の状態を参照せずに値を設定する場合に役立ちます。 useReducerフックは、前の値を参照する必要がある場合、または複雑なデータ操作を必要とするさまざまなアクションがある場合に役立ちます。 状態を設定するこれらのさまざまな方法を調べるために、オプションのリストから購入を追加することによって更新するショッピングカートを使用して製品ページコンポーネントを作成します。 このチュートリアルを終了すると、フックを使用して機能コンポーネントの状態を快適に管理できるようになり、 useEffect 、 useMemo 、などのより高度なフックの基盤が整います。およびuseContext。
前提条件
- Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.20.1およびnpmバージョン6.14.4でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
- Create React App でセットアップされたReact開発環境で、不要なボイラープレートが削除されています。 これを設定するには、ステップ1 —Reactクラスコンポーネントの状態を管理する方法のチュートリアルの空のプロジェクトを作成します。 このチュートリアルでは、プロジェクト名として
hooks-tutorialを使用します。 - また、 JavaScriptでコーディングする方法にあるJavaScriptの基本的な知識と、HTMLおよびCSSの基本的な知識も必要です。 HTMLとCSSの便利なリソースは、 Mozilla DeveloperNetworkです。
ステップ1-コンポーネントの初期状態を設定する
このステップでは、useStateフックを使用してカスタム変数に初期状態を割り当てることにより、コンポーネントの初期状態を設定します。 フックを探索するには、ショッピングカートを使用して商品ページを作成し、状態に基づいて初期値を表示します。 ステップの終わりまでに、フックを使用して状態値を保持するさまざまな方法と、小道具や静的な値ではなく状態を使用するタイミングを理解できます。
Productコンポーネントのディレクトリを作成することから始めます。
mkdir src/components/Product
次に、ProductディレクトリにあるProduct.jsというファイルを開きます。
nano src/components/Product/Product.js
状態のないコンポーネントを作成することから始めます。 コンポーネントは、アイテムの数と合計価格が表示されるカートと、カートにアイテムを追加またはカートから削除するためのボタンが表示される製品の2つの部分で構成されます。 今のところ、これらのボタンは機能しません。
次のコードをファイルに追加します。
フック-tutorial/src / components / Product / Product.js
import React from 'react';
import './Product.css';
export default function Product() {
return(
<div className="wrapper">
<div>
Shopping Cart: 0 total items.
</div>
<div>Total: 0</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
このコードでは、 JSXを使用してProductコンポーネントのHTML要素を作成し、アイスクリームの絵文字で製品を表現しました。 さらに、<div>要素の2つにはクラス名があるため、基本的なCSSスタイルを追加できます。
ファイルを保存して閉じ、ProductディレクトリにProduct.cssという名前の新しいファイルを作成します。
nano src/components/Product/Product.css
テキストと絵文字のフォントサイズを大きくするには、スタイルを追加します。
フック-tutorial/src / components / Product / Product.css
.product span {
font-size: 100px;
}
.wrapper {
padding: 20px;
font-size: 20px;
}
.wrapper button {
font-size: 20px;
background: none;
border: black solid 1px;
}
絵文字は製品イメージとして機能するため、はるかに大きなfont-sizeが必要になります。 さらに、backgroundをnoneに設定することにより、ボタンのデフォルトのグラデーションの背景を削除します。
ファイルを保存して閉じます。 次に、コンポーネントをAppコンポーネントに追加して、ブラウザーでProductコンポーネントをレンダリングします。 App.jsを開きます:
nano src/components/App/App.js
コンポーネントをインポートしてレンダリングします。 また、このチュートリアルでは使用しないため、CSSインポートを削除します。
フック-tutorial/src / components / App / App.js
import React from 'react';
import Product from '../Product/Product';
function App() {
return <Product />
}
export default App;
ファイルを保存して閉じます。 これを行うと、ブラウザが更新され、Productコンポーネントが表示されます。
動作するコンポーネントができたので、ハードコードされたデータを動的な値に置き換えることができます。
Reactは、メインのReactパッケージから直接インポートできるいくつかのフックをエクスポートします。 慣例により、ReactフックはuseState、useContext、useReducerなどの単語useで始まります。 ほとんどのサードパーティライブラリは同じ規則に従います。 たとえば、ReduxにはuseSelectorとuseStoreフックがあります。
フックは、Reactライフサイクルの一部としてアクションを実行できるようにする関数です。 フックは、他のアクションまたはコンポーネントの小道具の変更によってトリガーされ、データを作成するか、さらに変更をトリガーするために使用されます。 たとえば、useStateフックは、ステートフルなデータと、そのデータを変更して再レンダリングをトリガーする関数を生成します。 動的なコードを作成し、データが変更されたときに再レンダリングをトリガーすることでライフサイクルにフックします。 実際には、useStateフックを使用して動的なデータを変数に格納できることを意味します。
たとえば、このコンポーネントには、ユーザーの操作に基づいて変化する2つのデータがあります。カートと合計コストです。 これらのそれぞれは、上記のフックを使用して状態で保存できます。
これを試すには、Product.jsを開きます。
nano src/components/Product/Product.js
次に、強調表示されたコードを追加して、ReactからuseStateフックをインポートします。
フック-tutorial/src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
export default function Product() {
return(
<div className="wrapper">
<div>
Shopping Cart: 0 total items.
</div>
<div>Total: 0</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
useStateは、初期状態を引数として取り、2つの項目を含むarrayを返す関数です。 最初の項目は、JSXでよく使用する状態を含む変数です。 配列の2番目の項目は、状態を更新する関数です。 Reactはデータを配列として返すため、 destroying を使用して、任意の変数名に値を割り当てることができます。 つまり、useStateを何度も呼び出すことができ、名前の競合について心配する必要はありません。これは、すべての状態と更新関数を明確に名前が付けられた変数に割り当てることができるためです。
空の配列でuseStateフックを呼び出して、最初のフックを作成します。 次の強調表示されたコードを追加します。
フック-tutorial/src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
export default function Product() {
const [cart, setCart] = useState([]);
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: 0</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
ここでは、最初の値である状態をcartという変数に割り当てました。 cartは、カート内の製品を含む配列になります。 空の配列を引数としてuseStateに渡すことにより、初期の空の状態をcartの最初の値として設定します。
cart変数に加えて、setCartという変数に更新関数を割り当てました。 この時点では、setCart関数を使用しておらず、未使用の変数があることに関する警告が表示される場合があります。 今のところ、この警告は無視してください。 次のステップでは、setCartを使用してcartの状態を更新します。
ファイルを保存します。 ブラウザがリロードされると、変更なしでページが表示されます。
フックとクラスベースの状態管理の重要な違いの1つは、クラスベースの状態管理には単一の状態オブジェクトがあることです。 フックを使用すると、状態オブジェクトは互いに完全に独立しているため、必要な数の状態オブジェクトを持つことができます。 つまり、ステートフルデータの新しい部分が必要な場合は、新しいデフォルトでuseStateを呼び出し、その結果を新しい変数に割り当てるだけです。
Product.js内で、totalを保持する新しい状態を作成して、これを試してください。 デフォルト値を0に設定し、値と機能をtotalとsetTotalに割り当てます。
フック-tutorial/src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {total}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
ステートフルなデータが得られたので、表示されるデータを標準化して、より予測可能なエクスペリエンスを作成できます。 たとえば、この例の合計は価格であるため、常に小数点以下2桁になります。 toLocaleString メソッドを使用して、totalを数値から小数点以下2桁の文字列に変換できます。 また、ブラウザのロケールに一致する数値規則に従って、数値を文字列に変換します。 オプションminimumFractionDigitsおよびmaximumFractionDigitsを設定して、小数点以下の桁数を一定にします。
getTotalという関数を作成します。 この関数は、スコープ内変数totalを使用し、合計を表示するために使用するローカライズされた文字列を返します。 toLocaleStringの最初の引数としてundefinedを使用して、ロケールを指定するのではなく、システムロケールを使用します。
フック-tutorial/src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
const currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
function getTotal() {
return total.toLocaleString(undefined, currencyOptions)
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal()}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
これで、表示された合計に文字列処理が追加されました。 getTotalは別の関数ですが、周囲の関数と同じスコープを共有しているため、コンポーネントの変数を参照できます。
ファイルを保存します。 ページがリロードされ、小数点以下2桁で更新された合計が表示されます。
この関数は機能しますが、現時点では、getTotalはこのコードでのみ動作します。 この場合、それを純粋関数に変換できます。これは、同じ入力が与えられたときに同じ出力を提供し、動作するために特定の環境に依存しません。 関数を純粋関数に変換することで、再利用しやすくなります。 たとえば、別のファイルに抽出して、複数のコンポーネントで使用できます。
getTotalを更新して、totalを引数として取ります。 次に、関数をコンポーネントの外に移動します。
フック-tutorial/src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
const currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
function getTotal(total) {
return total.toLocaleString(undefined, currencyOptions)
}
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div><^>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
ファイルを保存します。 これを行うと、ページがリロードされ、以前と同じようにコンポーネントが表示されます。
このような機能コンポーネントを使用すると、機能を簡単に移動できます。 スコープの競合がない限り、これらの変換関数を任意の場所に移動できます。
このステップでは、useStateを使用してステートフルデータのデフォルト値を設定します。 次に、配列の破棄を使用して状態を変数に更新するためのステートフルデータと関数を保存しました。 次のステップでは、更新関数を使用して状態値を変更し、更新された情報でページを再レンダリングします。
ステップ2—useStateで状態を設定する
このステップでは、静的な値を使用して新しい状態を設定することにより、製品ページを更新します。 状態の一部を更新する関数を既に作成しているので、次に、事前定義された値で両方の状態変数を更新するイベントを作成します。 この手順を完了すると、ユーザーがボタンをクリックするだけで更新できる状態のページが作成されます。
クラスベースのコンポーネントとは異なり、1回の関数呼び出しで複数の状態を更新することはできません。 代わりに、各関数を個別に呼び出す必要があります。 これは、関心の分離が大きくなることを意味し、ステートフルなオブジェクトに焦点を合わせ続けるのに役立ちます。
カートにアイテムを追加し、アイテムの価格で合計を更新する関数を作成し、その関数を追加ボタンに追加します。
フック-tutorial/src / components / Product / Product.js
import React, { useState } from 'react';
...
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
function add() {
setCart(['ice cream']);
setTotal(5);
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button onClick={add}>Add</button><^>
<button>Remove</button>
</div>
)
}
このスニペットでは、「アイスクリーム」という単語を含む配列を使用してsetCartを呼び出し、5を使用してsetTotalを呼び出しました。 次に、この関数をAddボタンのonClickイベントハンドラーに追加しました。
関数は、状態を設定する関数と同じスコープを持っている必要があるため、コンポーネント関数内で定義する必要があることに注意してください。
ファイルを保存します。 これを行うと、ブラウザがリロードされ、追加ボタンをクリックすると、カートが現在の金額で更新されます。
thisコンテキストを参照していないため、arrow関数または関数宣言のいずれかを使用できます。 ここではどちらも同じように機能し、各開発者またはチームはどちらのスタイルを使用するかを決定できます。 追加の関数の定義をスキップして、関数をonClickプロパティに直接渡すこともできます。
これを試すには、カートを空のオブジェクトに設定し、合計を0に設定して、値を削除する関数を作成します。 削除ボタンのonClickプロップで関数を作成します。
フック-tutorial/src / component / Product / Product.js
import React, { useState } from 'react';
...
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
function add() {
setCart(['ice cream']);
setTotal(5);
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button onClick={add}>Add</button>
<button
onClick={() => {
setCart([]);
setTotal(0);
}}
>
Remove
</button>
</div>
)
}
ファイルを保存します。 これを行うと、アイテムを追加および削除できるようになります。
関数を割り当てるための両方の戦略は機能しますが、小道具で直接矢印関数を作成することには、パフォーマンスにわずかな影響があります。 再レンダリングするたびに、Reactは新しい関数を作成します。これにより、小道具の変更がトリガーされ、コンポーネントが再レンダリングされます。 小道具の外で関数を定義するときは、useCallbackと呼ばれる別のフックを利用できます。 これにより、関数がメモ化されます。つまり、特定の値が変更された場合にのみ、新しい関数が作成されます。 何も変更されない場合、プログラムは関数を再計算する代わりに、関数のキャッシュされたメモリを使用します。 一部のコンポーネントはそのレベルの最適化を必要としない場合がありますが、原則として、コンポーネントがツリー内にある可能性が高いほど、メモ化の必要性が高くなります。
このステップでは、useStateフックによって作成された関数を使用して状態データを更新しました。 両方の関数を呼び出して複数のデータの状態を同時に更新するラッピング関数を作成しました。 ただし、これらの関数は、前の状態を使用して新しい状態を作成する代わりに、静的な事前定義された値を追加するため、制限されています。 次のステップでは、useStateフックとuseReducerという新しいフックの両方で現在の状態を使用して状態を更新します。
ステップ3—現在の状態を使用して状態を設定する
前の手順で、静的な値で状態を更新しました。 以前の状態が何であるかは問題ではありませんでした。常に同じ値を渡しました。 ただし、一般的な商品ページには、カートに追加できる多くのアイテムが含まれているため、前のアイテムを保持したままカートを更新できるようにする必要があります。
このステップでは、現在の状態を使用して状態を更新します。 商品ページを拡張して複数の商品を含め、現在の値に基づいてカートと合計を更新する関数を作成します。 値を更新するには、useStateフックとuseReducerという新しいフックの両方を使用します。
Reactはアクションを非同期的に呼び出すことでコードを最適化する可能性があるため、関数が最新の状態にアクセスできることを確認する必要があります。 この問題を解決する最も基本的な方法は、値の代わりに関数を状態設定関数に渡すことです。 つまり、setState(5)を呼び出す代わりに、setState(previous => previous +5)を呼び出します。
これの実装を開始するには、オブジェクトのproducts配列を作成して製品ページにアイテムを追加し、Addおよびからイベントハンドラーを削除します。 ボタンを削除して、リファクタリングの余地を作ります。
フック-tutorial/src / component / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
...
const products = [
{
emoji: '🍦',
name: 'ice cream',
price: 5
},
{
emoji: '🍩',
name: 'donuts',
price: 2.5,
},
{
emoji: '🍉',
name: 'watermelon',
price: 4
}
];
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
function add() {
setCart(['ice cream']);
setTotal(5);
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div>
<div>
{products.map(product => (
<div key={product.name}>
<div className="product">
<span role="img" aria-label={product.name}>{product.emoji}</span>
</div>
<button>Add</button>
<button>Remove</button>
</div>
))}
<^></div><^
</div>
)
}
これで、 .mapメソッドを使用して配列を反復処理し、製品を表示するJSXができました。
ファイルを保存します。 これを行うと、ページがリロードされ、複数の製品が表示されます。
現在、ボタンにはアクションがありません。 クリック時に特定の商品を追加するだけなので、add関数の引数として商品を渡す必要があります。 add関数では、新しいアイテムをsetCartおよびsetTotal関数に直接渡す代わりに、現在の状態を取得して新しいを返す匿名関数を渡します更新された値:
フック-tutorial/src / component / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
...
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
function add(product) {
setCart(current => [...current, product.name]);
setTotal(current => current + product.price);
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div>
<div>
{products.map(product => (
<div key={product.name}>
<div className="product">
<span role="img" aria-label={product.name}>{product.emoji}</span>
</div>
<button onClick={() => add(product)}>Add</button>
<button>Remove</button>
</div>
))}
</div>
</div>
)
}
匿名関数は、新しい値を作成するために使用できる引数として、最新の状態(cartまたはtotal)を使用します。 ただし、状態を直接変更しないように注意してください。 代わりに、カートに新しい値を追加するときに、現在の値をスプレッドし、最後に新しい値を追加することで、新しい商品を状態に追加できます。
ファイルを保存します。 これを行うと、ブラウザがリロードされ、複数の製品を追加できるようになります。
useReducer と呼ばれる別のフックがあります。これは、 .reduce配列メソッドと同様に、現在の状態に基づいて状態を更新するように特別に設計されています。 useReducerフックはuseStateに似ていますが、フックを初期化するときに、初期データとともに状態を変更したときにフックが実行する関数を渡します。 この関数(reducerと呼ばれる)は、状態と別の引数の2つの引数を取ります。 もう1つの引数は、更新関数を呼び出すときに指定するものです。
カートの状態をリファクタリングして、useReducerフックを使用します。 stateとproductを引数として取るcartReducerという関数を作成します。 useStateをuseReducerに置き換えてから、cartReducer関数を最初の引数として渡し、空の配列を2番目の引数として渡します。これが初期データになります。
フック-tutorial/src / component / Product / Product.js
import React, { useReducer, useState } from 'react';
...
function cartReducer(state, product) {
return [...state, product]
}
export default function Product() {
const [cart, setCart] = useReducer(cartReducer, []);
const [total, setTotal] = useState(0);
function add(product) {
setCart(product.name);
setTotal(current => current + product.price);
}
return(
...
)
}
これで、setCartを呼び出すときに、関数の代わりに製品名を渡します。 setCartを呼び出すと、レデューサー関数が呼び出され、積が2番目の引数になります。 total状態でも同様の変更ができます。
現在の状態を取得して新しい金額を追加するtotalReducerという関数を作成します。 次に、useStateをuseReducerに置き換え、関数の代わりに新しい値setCartを渡します。
フック-tutorial/src / component / Product / Product.js
import React, { useReducer } from 'react';
...
function totalReducer(state, price) {
return state + price;
}
export default function Product() {
const [cart, setCart] = useReducer(cartReducer, []);
const [total, setTotal] = useReducer(totalReducer, 0);
function add(product) {
setCart(product.name);
setTotal(product.price);
}
return(
...
)
}
useStateフックを使用しなくなったため、インポートから削除しました。
ファイルを保存します。 これを行うと、ページがリロードされ、カートにアイテムを追加できるようになります。
次に、remove関数を追加します。 しかし、これは問題につながります。レデューサー関数はアイテムの追加と合計の更新を処理できますが、状態からのアイテムの削除をどのように処理できるかは明確ではありません。 レデューサー関数の一般的なパターンは、アクションの名前とアクションのデータを含む2番目の引数としてオブジェクトを渡すことです。 レデューサー内で、アクションに基づいて合計を更新できます。 この場合、addアクションでアイテムをカートに追加し、removeアクションでアイテムを削除します。
totalReducerから始めます。 actionを2番目の引数として取るように関数を更新してから、条件付きを追加して、action.typeに基づいて状態を更新します。
フック-tutorial/src / component / Product / Product.js
import React, { useReducer } from 'react';
import './Product.css';
...
function totalReducer(state, action) {
if(action.type === 'add') {
return state + action.price;
}
return state - action.price
}
export default function Product() {
const [cart, setCart] = useReducer(cartReducer, []);
const [total, setTotal] = useReducer(totalReducer, 0);
function add(product) {
const { name, price } = product;
setCart(name);
setTotal({ price, type: 'add' });
}
return(
...
)
}
actionは、typeとpriceの2つのプロパティを持つオブジェクトです。 タイプはaddまたはremoveのいずれかで、priceは数字です。 タイプがaddの場合、合計が増加します。 removeの場合、合計が下がります。 totalReducerを更新した後、typeのaddと、破壊割り当てを使用して設定したpriceを使用してsetTotalを呼び出します。
次に、cartReducerを更新します。 これはもう少し複雑です。if/then条件を使用できますが、switchステートメントを使用するのが一般的です。 Switchステートメントは、さまざまなアクションを処理できるレデューサーがある場合に特に役立ちます。これにより、これらのアクションがコードで読みやすくなります。
totalReducerと同様に、オブジェクトを2番目のアイテムtypeおよびnameプロパティとして渡します。 アクションがremoveの場合、製品の最初のインスタンスをスプライスして状態を更新します。
cartReducerを更新した後、type: 'remove'と[のいずれかを含むオブジェクトでsetCartとsetTotalを呼び出すremove関数を作成します。 X146X]またはname。 次に、switchステートメントを使用して、アクションタイプに基づいてデータを更新します。 必ず最終状態に戻してください。
フック-tutorial/src / complicated / Product / Product.js
import React, { useReducer } from 'react';
import './Product.css';
...
function cartReducer(state, action) {
switch(action.type) {
case 'add':
return [...state, action.name];
case 'remove':
const update = [...state];
update.splice(update.indexOf(action.name), 1);
return update;
default:
return state;
}
}
function totalReducer(state, action) {
if(action.type === 'add') {
return state + action.price;
}
return state - action.price
}
export default function Product() {
const [cart, setCart] = useReducer(cartReducer, []);
const [total, setTotal] = useReducer(totalReducer, 0);
function add(product) {
const { name, price } = product;
setCart({ name, type: 'add' });
setTotal({ price, type: 'add' });
}
function remove(product) {
const { name, price } = product;
setCart({ name, type: 'remove' });
setTotal({ price, type: 'remove' });
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(total)}</div>
<div>
{products.map(product => (
<div key={product.name}>
<div className="product">
<span role="img" aria-label={product.name}>{product.emoji}</span>
</div>
<button onClick={() => add(product)}>Add</button>
<button onClick={() => remove(product)}>Remove</button>
</div>
))}
</div>
</div>
)
}
コードで作業するときは、レデューサー関数の状態を直接変更しないように注意してください。 代わりに、splicingの前にオブジェクトをコピーしてください。 また、予期しないエッジケースを考慮して、switchステートメントにdefaultアクションを追加することをお勧めします。 この場合、caseはオブジェクトを返すだけです。 defaultの他のオプションは、エラーをスローするか、追加や削除などのアクションにフォールバックします。
変更を加えたら、ファイルを保存します。 ブラウザが更新されると、アイテムを追加および削除できるようになります。
この製品にはまだ微妙なバグが残っています。 remove方式では、商品がカートに入っていなくても価格から差し引くことができます。 カートに追加せずにアイスクリームの削除をクリックすると、表示される合計は-5.00になります。
アイテムを差し引く前にアイテムが存在することを確認することでこのバグを修正できますが、より効率的な方法は、関連データを1か所に保存するだけでさまざまな状態を最小限に抑えることです。 つまり、同じデータ(この場合は製品)への二重参照を避けるようにしてください。 代わりに、生データを1つの状態変数(製品オブジェクト全体)に格納し、そのデータを使用して計算を実行します。
add()関数が製品全体をレデューサーに渡し、remove()関数がオブジェクト全体を削除するように、コンポーネントをリファクタリングします。 getTotalメソッドはカートを使用するため、totalReducer関数を削除できます。 次に、カートをgetTotal()に渡すことができます。これをリファクタリングして、配列を単一の値に減らすことができます。
フック-tutorial/src / component / Product / Product.js
import React, { useReducer } from 'react';
import './Product.css';
const currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
function getTotal(cart) {
const total = cart.reduce((totalCost, item) => totalCost + item.price, 0);
return total.toLocaleString(undefined, currencyOptions)
}
...
function cartReducer(state, action) {
switch(action.type) {
case 'add':
return [...state, action.product];
case 'remove':
const productIndex = state.findIndex(item => item.name === action.product.name);
if(productIndex < 0) {
return state;
}
const update = [...state];
update.splice(productIndex, 1)
return update
default:
return state;
}
}
export default function Product() {
const [cart, setCart] = useReducer(cartReducer, []);
function add(product) {
setCart({ product, type: 'add' });
}
function remove(product) {
setCart({ product, type: 'remove' });
}
return(
<div className="wrapper">
<div>
Shopping Cart: {cart.length} total items.
</div>
<div>Total: {getTotal(cart)}</div>
<div>
{products.map(product => (
<div key={product.name}>
<div className="product">
<span role="img" aria-label={product.name}>{product.emoji}</span>
</div>
<button onClick={() => add(product)}>Add</button>
<button onClick={() => remove(product)}>Remove</button>
</div>
))}
</div>
</div>
)
}
ファイルを保存します。 これを行うと、ブラウザが更新され、最終的なカートが作成されます。
useReducerフックを使用すると、配列を解析およびスプライシングするための複雑なロジックがコンポーネントの外部にあるため、メインコンポーネントの本体が整理されて読みやすくなります。 レデューサーを再利用したい場合は、コンポーネントの外に移動することもできます。また、複数のコンポーネントで使用するカスタムフックを作成することもできます。 useState、useReducer、useEffectなどの基本的なフックを囲む関数としてカスタムフックを作成できます。
フックを使用すると、通常はコンポーネントにバインドされているクラスとは対照的に、ステートフルロジックをコンポーネントに出し入れすることができます。 この利点は、他のコンポーネントにも拡張できます。 フックは関数であるため、継承やその他の複雑な形式のクラス構成を使用するのではなく、複数のコンポーネントにフックをインポートできます。
このステップでは、現在の状態を使用して状態を設定する方法を学びました。 useStateフックとuseReducerフックの両方を使用して状態を更新するコンポーネントを作成し、バグを防ぎ、再利用性を向上させるために、コンポーネントを別のフックにリファクタリングしました。
結論
フックはReactの大きな変更であり、クラスを使用せずにロジックを共有してコンポーネントを更新する新しい方法を作成しました。 useStateとuseReducerを使用してコンポーネントを作成できるようになったので、ユーザーと動的な情報に応答する複雑なプロジェクトを作成するためのツールがあります。 また、より複雑なフックを探索したり、カスタムフックを作成したりするために使用できる知識の基礎があります。
Reactのチュートリアルをもっと見たい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。