Reactクラスコンポーネントの状態を管理する方法

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

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

序章

React では、 state は、アプリケーション内でデータが時間の経過とともにどのように変化するかを追跡する構造を指します。 状態の管理は、インタラクティブなコンポーネントや動的なWebアプリケーションを作成できるため、Reactの重要なスキルです。 状態は、フォーム入力の追跡からAPIからの動的データのキャプチャまですべてに使用されます。 このチュートリアルでは、クラスベースのコンポーネント状態を管理する例を実行します。

このチュートリアルの執筆時点で、公式の Reactドキュメントは、開発者が新しいコードを作成するときに、機能コンポーネントを使用するのではなく、Reactフックを採用して状態を管理することを推奨しています。 クラスベースのコンポーネント。 React Hooksの使用はより現代的な方法と考えられていますが、クラスベースのコンポーネントの状態を管理する方法を理解することも重要です。 状態管理の背後にある概念を学ぶことは、既存のコードベースでクラスベースの状態管理をナビゲートおよびトラブルシューティングするのに役立ち、クラスベースの状態管理がより適切である場合を決定するのに役立ちます。 componentDidCatch と呼ばれるクラスベースのメソッドもありますが、これはフックでは使用できず、クラスメソッドを使用して状態を設定する必要があります。

このチュートリアルでは、最初に静的な値を使用して状態を設定する方法を示します。これは、古い値をオーバーライドするAPIからのデータの設定など、次の状態が最初の状態に依存しない場合に役立ちます。 次に、状態を現在の状態として設定する方法について説明します。これは、値の切り替えなど、次の状態が現在の状態に依存する場合に役立ちます。 状態を設定するこれらのさまざまな方法を調べるために、オプションのリストから購入を追加することによって更新する製品ページコンポーネントを作成します。

前提条件

  • Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.20.1およびnpmバージョン6.14.4でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
  • このチュートリアルでは、 Create ReactAppを使用してアプリを作成します。 Create React Appを使用してアプリケーションをインストールする手順については、 Create ReactAppを使用してReactプロジェクトをセットアップする方法を参照してください。
  • また、 JavaScriptでコーディングする方法にあるJavaScriptの基本的な知識と、HTMLおよびCSSの基本的な知識も必要です。 HTMLとCSSの優れたリソースは、 Mozilla DeveloperNetworkです。

ステップ1—空のプロジェクトを作成する

このステップでは、 Create ReactAppを使用して新しいプロジェクトを作成します。 次に、プロジェクトをブートストラップするときにインストールされるサンプルプロジェクトと関連ファイルを削除します。 最後に、コンポーネントを整理するための単純なファイル構造を作成します。 これにより、クラスベースのコンポーネントの状態を管理するためのこのチュートリアルのサンプルアプリケーションを構築するための強固な基盤が得られます。

まず、新しいプロジェクトを作成します。 ターミナルで次のスクリプトを実行し、create-react-appを使用して新しいプロジェクトをインストールします。

npx create-react-app state-class-tutorial

プロジェクトが終了したら、次のディレクトリに移動します。

cd state-class-tutorial

新しいターミナルタブまたはウィンドウで、 CreateReactApp開始スクリプトを使用してプロジェクトを開始します。 ブラウザは変更時に自動更新されるため、作業中はこのスクリプトを実行したままにします。

npm start

実行中のローカルサーバーを取得します。 プロジェクトがブラウザウィンドウで開かなかった場合は、 http:// localhost:3000/でプロジェクトを開くことができます。 これをリモートサーバーから実行している場合、アドレスはhttp://your_domain:3000になります。

ブラウザには、CreateReactAppの一部として含まれている単純なReactアプリケーションが読み込まれます。

完全に新しいカスタムコンポーネントのセットを構築するので、空のプロジェクトを作成できるように、ボイラープレートコードをクリアすることから始める必要があります。

まず、テキストエディタでsrc/App.jsを開きます。 これは、ページに挿入されるルートコンポーネントです。 すべてのコンポーネントはここから始まります。 App.jsの詳細については、 Create ReactAppを使用してReactプロジェクトをセットアップする方法を参照してください。

次のコマンドでsrc/App.jsを開きます。

nano src/App.js

次のようなファイルが表示されます。

state-class-tutorial / src / App.js

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

import logo from './logo.svg';の行を削除します。 次に、returnステートメントのすべてを置き換えて、空のタグのセット<></>を返します。 これにより、何も返さない有効なページが表示されます。 最終的なコードは次のようになります。

state-class-tutorial / src / App.js

import React from 'react';
import './App.css';

function App() {
  return <></>;
}

export default App;

テキストエディタを保存して終了します。

最後に、ロゴを削除します。 アプリケーションで使用することはないので、作業中に未使用のファイルを削除する必要があります。 長期的には混乱からあなたを救うでしょう。

ターミナルウィンドウで、次のコマンドを入力します。

rm src/logo.svg

ブラウザを見ると、空白の画面が表示されます。

サンプルのCreateReactAppプロジェクトをクリアしたので、単純なファイル構造を作成します。 これにより、コンポーネントを分離して独立させることができます。

srcディレクトリにcomponentsというディレクトリを作成します。 これにより、すべてのカスタムコンポーネントが保持されます。

mkdir src/components

各コンポーネントには、スタイル、画像、テストとともにコンポーネントファイルを保存するための独自のディレクトリがあります。

Appのディレクトリを作成します。

mkdir src/components/App

すべてのAppファイルをそのディレクトリに移動します。 ワイルドカード*を使用して、ファイル拡張子に関係なく、App.で始まるファイルを選択します。 次に、mvコマンドを使用して、それらを新しいディレクトリに配置します。

mv src/App.* src/components/App

次に、index.jsの相対インポートパスを更新します。これは、プロセス全体をブートストラップするルートコンポーネントです。

nano src/index.js

importステートメントは、AppディレクトリのApp.jsファイルを指す必要があるため、次の強調表示された変更を行います。

state-class-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';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </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();

ファイルを保存して終了します。

プロジェクトが設定されたので、最初のコンポーネントを作成できます。

ステップ2—コンポーネントでの状態の使用

このステップでは、コンポーネントの初期状態をそのクラスに設定し、その状態を参照して値を表示します。 次に、状態値を使用してカート内の合計アイテムを表示するショッピングカートを使用して製品ページを作成します。 ステップの終わりまでに、値を保持するさまざまな方法と、小道具や静的な値ではなく状態を使用する必要がある場合を理解できます。

コンポーネントの構築

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

mkdir src/components/Product

次に、そのディレクトリでProduct.jsを開きます。

nano src/components/Product/Product.js

状態のないコンポーネントを作成することから始めます。 コンポーネントには2つの部分があります。アイテムの数と合計価格が表示されるカートと、アイテムを追加および削除するためのボタンが表示される製品です。 今のところ、ボタンにはアクションがありません。

次のコードをProduct.jsに追加します。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {
  render() {
    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クラス名を持つdiv要素をいくつか含めたので、基本的なスタイルを追加できます。

ファイルを保存して閉じ、Product.cssを開きます。

nano src/components/Product/Product.css

テキストと絵文字のfont-sizeを増やすために、軽いスタイルを設定します。

state-class-tutorial / src / components / Product / Product.css

.product span {
    font-size: 100px;
}

.wrapper {
    padding: 20px;
    font-size: 20px;
}

.wrapper button {
    font-size: 20px;
    background: none;
}

この例では絵文字が製品画像として機能するため、絵文字にはテキストよりもはるかに大きなフォントサイズが必要になります。 さらに、backgroundnoneに設定することにより、ボタンのデフォルトのグラデーションの背景を削除します。

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

次に、ProductコンポーネントをAppコンポーネントでレンダリングして、ブラウザで結果を確認できるようにします。 App.jsを開きます:

nano src/components/App/App.js

コンポーネントをインポートしてレンダリングします。 このチュートリアルでは使用しないため、CSSインポートを削除することもできます。

state-class-tutorial / src / components / App / App.js

import React from 'react';
import Product from '../Product/Product';

function App() {
  return <Product />
}

export default App;

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

クラスコンポーネントの初期状態の設定

表示で変更されるコンポーネント値には、アイテムの総数とコストの2つの値があります。 それらをハードコーディングする代わりに、このステップでは、それらをstateと呼ばれるオブジェクトに移動します。

Reactクラスのstateは、ページのレンダリングを制御する特別なプロパティです。 状態を変更すると、Reactはコンポーネントが古くなっていることを認識し、自動的に再レンダリングします。 コンポーネントが再レンダリングされると、レンダリングされた出力が変更され、stateの最新情報が含まれます。 この例では、商品をカートに追加したり、カートから削除したりするたびに、コンポーネントが再レンダリングされます。 Reactクラスに他のプロパティを追加することはできますが、それらには再レンダリングをトリガーする同じ機能はありません。

Product.jsを開きます:

nano src/components/Product/Product.js

stateというプロパティをProductクラスに追加します。 次に、stateオブジェクトにcarttotalの2つの値を追加します。 cartは、最終的に多くのアイテムを保持する可能性があるため、配列になります。 totalは数字になります。 これらを割り当てた後、値への参照をthis.state.propertyに置き換えます。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.state.total}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button>Add</button> <button>Remove</button>
      </div>
    )
  }
}

どちらの場合も、JSX内でJavaScriptを参照しているため、コードを中括弧で囲む必要があることに注意してください。 また、cart配列のlengthを表示して、配列内のアイテム数のカウントを取得しています。

ファイルを保存します。 これを行うと、ブラウザが更新され、以前と同じページが表示されます。

stateプロパティは標準のクラスプロパティです。つまり、renderメソッドだけでなく、他のメソッドでもアクセスできます。

次に、価格を静的な値として表示する代わりに、 toLocaleString メソッドを使用して文字列に変換します。これにより、ブラウザの領域での数値の表示方法に一致する文字列に数値が変換されます。

stateを取得し、currencyOptionsの配列を使用してローカライズされた文字列に変換する、getTotal()というメソッドを作成します。 次に、JSXのstateへの参照をメソッド呼び出しに置き換えます。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions)
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button>Add</button> <button>Remove</button>
      </div>
    )
  }
}

totalは商品の価格であるため、totalの小数点以下の最大値と最小値を2に設定するcurrencyOptionsを渡します。 これは別のプロパティとして設定されていることに注意してください。 多くの場合、初心者のReact開発者は、このような情報をstateオブジェクトに配置しますが、変更が予想されるstateにのみ情報を追加することをお勧めします。 このように、stateの情報は、アプリケーションの拡張に合わせて追跡しやすくなります。

行ったもう1つの重要な変更は、矢印関数をクラスプロパティに割り当ててgetTotal()メソッドを作成することでした。 矢印関数を使用しない場合、このメソッドは新しいこのバインディングを作成します。これは、現在のthisバインディングに干渉し、コードにバグをもたらします。 これについては、次のステップで詳しく説明します。

ファイルを保存します。 これを行うと、ページが更新され、値が小数に変換されて表示されます。

これで、コンポーネントに状態が追加され、クラスで参照されました。 renderメソッドおよび他のクラスメソッドの値にもアクセスしました。 次に、状態を更新して動的な値を表示するメソッドを作成します。

ステップ3—静的な値から状態を設定する

これまで、コンポーネントの基本状態を作成し、関数とJSXコードでその状態を参照しました。 このステップでは、ボタンのクリックでstateを変更するように、製品ページを更新します。 更新された値を含む新しいオブジェクトをsetStateという特別なメソッドに渡す方法を学習します。このメソッドは、更新されたデータでstateを設定します。

stateを更新するために、React開発者は、基本Componentクラスから継承されたsetStateと呼ばれる特別なメソッドを使用します。 setStateメソッドは、最初の引数としてオブジェクトまたは関数のいずれかを取ることができます。 stateを参照する必要のない静的な値がある場合は、読みやすいため、新しい値を含むオブジェクトを渡すのが最適です。 現在の状態を参照する必要がある場合は、古いstateへの参照を回避する関数を渡します。

ボタンにイベントを追加することから始めます。 ユーザーが追加をクリックすると、プログラムはアイテムをcartに追加し、totalを更新します。 削除をクリックすると、カートが空のアレイにリセットされ、total0にリセットされます。 たとえば、プログラムでは、ユーザーがアイテムを2回以上追加することはできません。

Product.jsを開きます:

nano src/components/Product/Product.js

コンポーネント内で、addという新しいメソッドを作成し、そのメソッドをAddボタンのonClickプロップに渡します。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  add = () => {
    this.setState({
      cart: ['ice cream'],
      total: 5
    })
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions)
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button onClick={this.add}>Add</button>
        <button>Remove</button>
      </div>
    )
  }
}

addメソッド内で、setStateメソッドを呼び出し、更新されたcartを含むオブジェクトを単一のアイテムice creamと更新された価格5。 再び矢印関数を使用してaddメソッドを作成したことに注意してください。 前述のように、これにより、更新の実行時に関数に適切なthisコンテキストが確保されます。 矢印関数を使用せずにメソッドとして関数を追加すると、 [1] 関数が現在のコンテキストにバインドされていないと、setStateは存在しません。

たとえば、add関数を次のように作成した場合:

export default class Product extends Component {
...
  add() {
    this.setState({
      cart: ['ice cream'],
      total: 5
    })
  }
...
}

追加ボタンをクリックするとエラーが発生します。

矢印関数を使用すると、このエラーを回避するための適切なコンテキストを確保できます。

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

addメソッドを使用して、stateオブジェクトの両方のプロパティcarttotalを渡しました。 ただし、必ずしも完全なオブジェクトを渡す必要はありません。 更新するプロパティを含むオブジェクトを渡すだけで、他のすべては同じままになります。

Reactがより小さなオブジェクトをどのように処理できるかを確認するには、removeという新しい関数を作成します。 空の配列を持つcartのみを含む新しいオブジェクトを渡し、RemoveボタンのonClickプロパティにメソッドを追加します。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {

  ...
  remove = () => {
    this.setState({
      cart: []
    })
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</div>

        <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
        <button onClick={this.add}>Add</button>
        <button onClick={this.remove}>Remove</button>
      </div>
    )
  }
}

ファイルを保存します。 ブラウザが更新されたら、追加ボタンと削除ボタンをクリックします。 カートの更新は表示されますが、価格は表示されません。 total状態の値は、更新中に保持されます。 この値は、例としてのみ保持されます。 このアプリケーションでは、stateオブジェクトの両方のプロパティを更新する必要があります。 ただし、多くの場合、異なる責任を持つステートフルプロパティを持つコンポーネントがあり、更新されたオブジェクトからそれらを除外することで、それらを永続化できます。

このステップでの変更は静的でした。 事前に値がどうなるかを正確に知っていたので、stateから値を再計算する必要はありませんでした。 ただし、商品ページに多数の商品があり、それらを複数回追加できるようにしたい場合、静的オブジェクトを渡すと、オブジェクトがthis.stateの値。 この場合、代わりに関数を使用できます。

次のステップでは、現在の状態を参照する関数を使用してstateを更新します。

ステップ4—現在の状態を使用して状態を設定する

配列の更新、数値の追加、オブジェクトの変更など、現在の状態を更新するために以前の状態を参照する必要がある場合がよくあります。 できるだけ正確にするには、最新のstateオブジェクトを参照する必要があります。 stateを事前定義された値で更新するのとは異なり、このステップでは、関数をsetStateメソッドに渡します。このメソッドは、現在の状態を引数として取ります。 このメソッドを使用して、現在の状態を使用してコンポーネントの状態を更新します。

stateを機能で設定するもう1つの利点は、信頼性が向上することです。 パフォーマンスを向上させるために、ReactはsetState呼び出しをバッチ処理する場合があります。つまり、this.state.valueは完全に信頼できない場合があります。 たとえば、stateを複数の場所ですばやく更新すると、値が古くなる可能性があります。 これは、データのフェッチ、フォームの検証、または複数のアクションが並行して発生している状況で発生する可能性があります。 ただし、引数として最新のstateを使用する関数を使用すると、このバグがコードに入力されないことが保証されます。

この形式の状態管理を示すために、製品ページにさらにいくつかの項目を追加します。 まず、Product.jsファイルを開きます。

nano src/components/Product/Product.js

次に、さまざまな製品のオブジェクトの配列を作成します。 配列には、商品の絵文字、名前、価格が含まれます。 次に、アレイをループして、追加および削除ボタンで各製品を表示します。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } 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 class Product extends Component {

  ...


  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</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={this.add}>Add</button>
              <button onClick={this.remove}>Remove</button>
            </div>
          ))}
        </div>
      </div>
    )
  }
}

このコードでは、 map()配列メソッドを使用してproducts配列をループし、ブラウザーに各要素を表示するJSXを返します。

ファイルを保存します。 ブラウザがリロードされると、更新された製品リストが表示されます。

次に、メソッドを更新する必要があります。 まず、add()メソッドを変更して、productを引数として取ります。 次に、オブジェクトをsetState()に渡す代わりに、stateを引数として取り、cartが新製品で更新されたオブジェクトと[ X186X] が新しい価格で更新されました:

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

...

export default class Product extends Component {

  state = {
    cart: [],
    total: 0
  }

  add = (product) => {
    this.setState(state => ({
      cart: [...state.cart, product.name],
      total: state.total + product.price
    }))
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions)
  }

  remove = () => {
    this.setState({
      cart: []
    })
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</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={() => this.add(product)}>Add</button>
              <button onClick={this.remove}>Remove</button>
            </div>
          ))}
        </div>
      </div>
    )
  }
}

setState()に渡す無名関数内で、コンポーネントの状態(this.state)ではなく、引数(state)を参照していることを確認してください。 そうしないと、古いstateオブジェクトを取得するリスクがあります。 関数内のstateは、それ以外は同じになります。

状態を直接変更しないように注意してください。 代わりに、cartに新しい値を追加するときに、現在の値にスプレッド構文を使用して、新しいproductstateに追加できます。最後に新しい値を追加します。

最後に、onClick()プロップを変更して、this.addの呼び出しを更新し、関連する製品でthis.add()を呼び出す無名関数を取得します。

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

次に、remove()メソッドを更新します。 同じ手順に従います。setStateを変換して関数を取得し、変更せずに値を更新し、onChange()プロパティを更新します。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

...

export default class Product extends Component {

...

  remove = (product) => {
    this.setState(state => {
      const cart = [...state.cart];
      cart.splice(cart.indexOf(product.name))
      return ({
        cart,
        total: state.total - product.price
      })
    })
  }

  render() {
    return(
      <div className="wrapper">
        <div>
          Shopping Cart: {this.state.cart.length} total items.
        </div>
        <div>Total {this.getTotal()}</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={() => this.add(product)}>Add</button>
              <button onClick={() => this.remove(product)}>Remove</button>
            </div>
          ))}
        </div>
      </div>
    )
  }
}

状態オブジェクトの変更を回避するには、最初にspread演算子を使用してそのコピーを作成する必要があります。 次に、コピーから必要なアイテムをスプライスして、新しいオブジェクトにコピーを返すことができます。 最初のステップとしてstateをコピーすることにより、stateオブジェクトを変更しないようにすることができます。

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

このアプリケーションにはまだバグがあります。removeメソッドでは、アイテムがcartにない場合でも、ユーザーはtotalから減算できます。 カートに追加せずにアイスクリームの削除をクリックすると、合計は-5.00になります。

減算する前にアイテムの存在を確認することでバグを修正できますが、より簡単な方法は、製品への参照のみを保持し、製品への参照と合計コストを分離しないことで、状態オブジェクトを小さく保つことです。 同じデータへの二重参照を避けるようにしてください。 代わりに、生データをstate(この場合はproductオブジェクト全体)に保存してから、stateの外部で計算を実行します。

add()メソッドがオブジェクト全体を追加し、remove()メソッドがオブジェクト全体を削除し、getTotalメソッドがcartを使用するように、コンポーネントをリファクタリングします。

state-class-tutorial / src / components / Product / Product.js

import React, { Component } from 'react';
import './Product.css';

...

export default class Product extends Component {

  state = {
    cart: [],
  }

  add = (product) => {
    this.setState(state => ({
      cart: [...state.cart, product],
    }))
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    const total = this.state.cart.reduce((totalCost, item) => totalCost + item.price, 0);
    return total.toLocaleString(undefined, this.currencyOptions)
  }

  remove = (product) => {
    this.setState(state => {
      const cart = [...state.cart];
      const productIndex = cart.findIndex(p => p.name === product.name);
      if(productIndex < 0) {
        return;
      }
      cart.splice(productIndex, 1)
      return ({
        cart
      })
    })
  }

  render() {
    ...
  }
}

add()メソッドは、totalプロパティへの参照が削除されていることを除いて、以前と同じです。 remove()メソッドでは、productfindByIndexのインデックスが見つかります。 インデックスが存在しない場合は、-1を取得します。 その場合、条件文を使用して何も返しません。 何も返さないことで、Reactはstateが変更されていないことを認識し、再レンダリングをトリガーしません。 stateまたは空のオブジェクトを返した場合でも、再レンダリングがトリガーされます。

splice()メソッドを使用する場合、2番目の引数として1を渡すことになります。これにより、1つの値が削除され、残りが保持されます。

最後に、 reduce()配列メソッドを使用してtotalを計算します。

ファイルを保存します。 これを行うと、ブラウザが更新され、最終的なcartが作成されます。

渡すsetState関数には、現在の小道具の追加の引数を含めることができます。これは、現在の小道具を参照する必要がある状態がある場合に役立ちます。 最初の引数にオブジェクトまたは関数を渡すかどうかに関係なく、2番目の引数としてsetStateにコールバック関数を渡すこともできます。 これは、APIからデータをフェッチした後にstateを設定し、stateの更新が完了した後に新しいアクションを実行する必要がある場合に特に便利です。

このステップでは、現在の状態に基づいて新しい状態を更新する方法を学習しました。 setState関数に関数を渡し、現在の状態を変更せずに新しい値を計算しました。 また、再レンダリングを妨げるような方法で更新がない場合にsetState関数を終了する方法を学び、パフォーマンスをわずかに向上させました。

結論

このチュートリアルでは、静的に更新し、現在の状態を使用する動的な状態を持つクラスベースのコンポーネントを開発しました。 これで、ユーザーと動的な情報に対応する複雑なプロジェクトを作成するためのツールが手に入りました。

Reactにはフックを使用して状態を管理する方法がありますが、componentDidCatchメソッドを使用するコンポーネントなど、クラスベースである必要があるコンポーネントを操作する必要がある場合は、コンポーネントで状態を使用する方法を理解しておくと役立ちます。 。

状態の管理は、ほぼすべてのコンポーネントにとって重要であり、インタラクティブなアプリケーションを作成するために必要です。 この知識があれば、スライダー、アコーディオン、フォームなど、多くの一般的なWebコンポーネントを再作成できます。 次に、フックを使用してアプリケーションを構築したり、APIから動的にデータをプルするコンポーネントを開発したりするのと同じ概念を使用します。

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