序章

React 開発では、アプリケーションデータが時間の経過とともにどのように変化するかを追跡することを、状態管理と呼びます。 アプリケーションの状態を管理することで、ユーザーの入力に応答する動的なアプリを作成できるようになります。 クラスベースの状態管理Reduxなどのサードパーティライブラリなど、Reactで状態を管理する方法はたくさんあります。 このチュートリアルでは、公式のReactドキュメント:フックで推奨されている方法を使用して、機能コンポーネントの状態を管理します。

フックは、コンポーネントのpropsが変更されたときにカスタム関数を実行する幅広いツールセットです。 この状態管理の方法ではクラスを使用する必要がないため、開発者はフックを使用して、共有と保守が容易な、より短く、より読みやすいコードを記述できます。 フックとクラスベースの状態管理の主な違いの1つは、すべての状態を保持する単一のオブジェクトがないことです。 代わりに、状態を複数の部分に分割して、個別に更新することができます。

このチュートリアルでは、useStateおよびuseReducerフックを使用して状態を設定する方法を学習します。 The useState フックは、現在の状態を参照せずに値を設定するときに役立ちます。 the useReducer フックは、前の値を参照する必要がある場合、または複雑なデータ操作を必要とするさまざまなアクションがある場合に役立ちます。 状態を設定するこれらのさまざまな方法を調べるために、オプションのリストから購入を追加することによって更新するショッピングカートを使用して製品ページコンポーネントを作成します。 このチュートリアルを終了すると、フックを使用して機能コンポーネントの状態を快適に管理できるようになり、 useEffect useMemo 、およびuseContext

前提条件

ステップ1-コンポーネントの初期状態を設定する

このステップでは、を使用してカスタム変数に初期状態を割り当てることにより、コンポーネントの初期状態を設定します。 useState 針。 フックを探索するには、ショッピングカートを使用して商品ページを作成し、状態に基づいて初期値を表示します。 ステップの終わりまでに、フックを使用して状態値を保持するさまざまな方法と、小道具や静的な値ではなく状態を使用するタイミングを理解できます。

のディレクトリを作成することから始めます Product 成分:

  1. mkdir src/components/Product

次に、というファイルを開きます Product.js の中に Product ディレクトリ:

  1. 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 コンポーネント、製品を表すアイスクリームの絵文字付き。 さらに、2つ <div> 要素にはクラス名があるため、基本的なCSSスタイルを追加できます。

ファイルを保存して閉じてから、という名前の新しいファイルを作成します Product.css の中に Product ディレクトリ:

  1. 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、製品イメージとして機能しているため。 さらに、設定することにより、ボタンのデフォルトのグラデーションの背景を削除します backgroundnone.

ファイルを保存して閉じます。 次に、コンポーネントをに追加します App レンダリングするコンポーネント Product ブラウザのコンポーネント。 開ける App.js:

  1. 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フックは単語で始まります use、 そのような useState, useContext、 と useReducer. ほとんどのサードパーティライブラリは同じ規則に従います。 たとえば、ReduxにはuseSelectorとuseStoreフックがあります。

フックは、Reactライフサイクルの一部としてアクションを実行できるようにする関数です。 フックは、他のアクションまたはコンポーネントの小道具の変更によってトリガーされ、データを作成するか、さらに変更をトリガーするために使用されます。 たとえば、 useState フックは、ステートフルデータと、そのデータを変更して再レンダリングをトリガーする関数を生成します。 動的なコードを作成し、データが変更されたときに再レンダリングをトリガーすることでライフサイクルにフックします。 実際には、これは、動的なデータを変数に格納できることを意味します。 useState 針。

たとえば、このコンポーネントには、ユーザーの操作に基づいて変化する2つのデータがあります。カートと合計コストです。 これらのそれぞれは、上記のフックを使用して状態で保存できます。

これを試すには、開いてください Product.js:

  1. nano src/components/Product/Product.js

次に、 useState 強調表示されたコードを追加して、Reactからフックします。

フック-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 値と関数をに割り当てます totalsetTotal:

フック-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桁の文字列まで。 また、ブラウザのロケールに一致する数値規則に従って、数値を文字列に変換します。 オプションを設定します minimumFractionDigitsmaximumFractionDigits 小数点以下の桁数を一定にするため。

と呼ばれる関数を作成します getTotal. この関数はスコープ内変数を使用します total 合計を表示するために使用するローカライズされた文字列を返します。 使用する undefined の最初の引数として toLocaleString ロケールを指定するのではなく、システムロケールを使用するには:

フック-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 「アイスクリーム」という単語を含む配列で、 setTotal5. 次に、この関数をに追加しました onClick 追加ボタンのイベントハンドラー。

関数は、状態を設定する関数と同じスコープを持っている必要があるため、コンポーネント関数内で定義する必要があることに注意してください。

ファイルを保存します。 これを行うと、ブラウザがリロードされ、追加ボタンをクリックすると、カートが現在の金額で更新されます。

あなたは参照していないので this コンテキストでは、矢印関数または関数宣言のいずれかを使用できます。 ここではどちらも同じように機能し、各開発者またはチームはどちらのスタイルを使用するかを決定できます。 追加の関数の定義をスキップして、関数を直接に渡すこともできます。 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 オブジェクトの配列、次に追加および削除ボタンからイベントハンドラーを削除して、リファクタリングの余地を作ります。

フック-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 新しいアイテムを直接に渡す代わりに、関数 setCartsetTotal 関数の場合、現在の状態を取得して新しい更新された値を返す無名関数を渡します。

フック-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配列メソッドと同様に、現在の状態に基づいて状態を更新するように特別に設計されています。 The useReducer フックはに似ています useStateただし、フックを初期化するときは、初期データとともに状態を変更したときにフックが実行する関数を渡します。 関数-と呼ばれる reducer—状態と別の引数の2つの引数を取ります。 もう1つの引数は、更新関数を呼び出すときに指定するものです。

カートの状態をリファクタリングして、 useReducer 針。 と呼ばれる関数を作成します cartReducer それはかかります state そしてその product 引数として。 交換 useStateuseReducer、次に合格 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、reducer関数を呼び出し、積が2番目の引数になります。 あなたはで同様の変更を加えることができます total 州。

と呼ばれる関数を作成します totalReducer 現在の状態を取得し、新しい金額を追加します。 その後、交換してください useStateuseReducer 新しい値を渡します 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番目の引数として、 conditional を追加して、に基づいて状態を更新します。 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(
    ...
  )
}

The action 2つのプロパティを持つオブジェクトです。 typeprice. タイプはどちらでもかまいません add また remove、 そしてその price は数字です。 タイプが add、合計が増えます。 もしそれが remove、合計を下げます。 更新後 totalReducer、 あなたが呼ぶ setTotal とともに typeadd そしてその price、破壊代入を使用して設定します。

次に、を更新します cartReducer. これはもう少し複雑です:あなたは使うことができます if/then 条件付きですが、switchステートメントを使用するのが一般的です。 Switchステートメントは、さまざまなアクションを処理できるレデューサーがある場合に特に役立ちます。これにより、これらのアクションがコードで読みやすくなります。

と同じように totalReducer、2番目のアイテムとしてオブジェクトを渡します typename プロパティ。 アクションが remove、製品の最初のインスタンスをスプライスして状態を更新します。

更新後 cartReducer、作成する remove 呼び出す関数 setCartsetTotal を含むオブジェクトで type: 'remove' そしてどちらか price または 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 オブジェクトを出します。 また、追加するのがベストプラクティスであることに注意してください default 予期しないエッジケースを説明するためのswitchステートメントに対するアクション。 この場合、caseはオブジェクトを返すだけです。 その他のオプション default エラーをスローしたり、追加や削除などのアクションにフォールバックしたりしています。

変更を加えたら、ファイルを保存します。 ブラウザが更新されると、アイテムを追加および削除できるようになります。

この製品にはまだ微妙なバグが残っています。 の中に remove 方法では、アイテムがカートに入っていなくても価格から差し引くことができます。 カートに追加せずにアイスクリームの削除をクリックすると、表示される合計は-5.00になります。

アイテムを差し引く前にアイテムが存在することを確認することでこのバグを修正できますが、より効率的な方法は、関連データを1か所に保存するだけでさまざまな状態を最小限に抑えることです。 つまり、同じデータ(この場合は製品)への二重参照を避けるようにしてください。 代わりに、生データを1つの状態変数(製品オブジェクト全体)に格納し、そのデータを使用して計算を実行します。

コンポーネントをリファクタリングして、 add() 関数は製品全体をレデューサーと remove() 関数はオブジェクト全体を削除します。 The 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の大きな変更であり、クラスを使用せずにロジックを共有してコンポーネントを更新する新しい方法を作成しました。 これで、を使用してコンポーネントを作成できます useStateuseReducer、ユーザーと動的な情報に対応する複雑なプロジェクトを作成するためのツールがあります。 また、より複雑なフックを探索したり、カスタムフックを作成したりするために使用できる知識の基礎があります。

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