Reactの状態管理の3つのルールのまとめ

Reactの状態管理の3つのルールのまとめ

序文

React コンポーネント内の状態は、レンダリング パス間で保持されるカプセル化されたデータです。 useState() は、機能コンポーネント内の状態を管理する React フックです。

私は useState() が大好きです。これを使うと状態の処理が非常に簡単になります。しかし、私はよく似たような問題に遭遇します:

  • コンポーネントの状態を小さな状態に分割するべきでしょうか、それとも複合状態として保持するべきでしょうか?
  • 状態管理が複雑になる場合は、コンポーネントから抽出する必要がありますか?何をするか?
  • useState() の使い方が簡単なら、useReducer() はいつ必要になるのでしょうか?

この記事では、上記の質問に答え、コンポーネントの状態を設計するのに役立つ 3 つの簡単なルールを紹介します。

No.1 焦点

効果的な状態管理の第一のルールは次のとおりです。

状態変数が問題の原因であることを確認します。

状態変数が 1 つの懸念事項を担当するようにすると、単一責任の原則に準拠することになります。

複合状態、つまり複数の状態値を含む状態の例を見てみましょう。

const [状態、setState] = useState({
    オン: 真、
    カウント: 0
});

state.on // => true
状態.count // => 0

状態は、on プロパティと count プロパティを持つ単純な JavaScript オブジェクトで構成されます。

最初のプロパティ state.on には、スイッチを示すブール値が含まれています。同様に、「state.count」には、ユーザーがボタンをクリックした回数などのカウンターを表す数値が含まれています。

次に、カウンターを 1 増やしたいとします。

// 複合状態の更新
ユーザー設定({
    ...州、
    カウント: 状態.count + 1
});

カウントのみを更新するには、状態全体をまとめておく必要があります。これは、単にカウンターを増分するためだけに呼び出すには大きな構造です。これはすべて、状態変数がスイッチとカウンターの 2 つの役割を担っているためです。

解決策は、複合状態を 2 つの原子状態に分割してカウントすることです。

定数[on, setOnOff] = useState(true);
定数[count, setCount] = useState(0);

状態変数 on はスイッチの状態を保存する役割のみを果たします。繰り返しますが、count 変数はカウンターのみを担当します。

それでは、カウンターを更新してみましょう。

setCount(カウント + 1);
// またはコールバックを使用する
setCount(count => count + 1);

カウント状態は単なるカウントであり、簡単に理解、更新、および読み取りできます。

各懸念事項の状態変数を作成するために複数の useState() を呼び出す必要はありません。

ただし、useState() 変数を多用しすぎると、コンポーネントが「単一責任の原則」に違反する可能性があることに注意してください。そのようなコンポーネントを小さなコンポーネントに分割するだけです。

No.2 複雑な状態ロジックの抽出

複雑な状態ロジックをカスタム フックに抽出します。

複雑な状態操作をコンポーネント内に保持することは意味がありますか?

答えは基礎から来ます(通常そうなります)。

React フックは、複雑な状態管理や副作用からコンポーネントを分離するために作成されました。したがって、コンポーネントはどの要素をレンダリングするか、どのイベント リスナーをアタッチするかだけを考慮する必要があるため、複雑な状態ロジックはカスタム フックに抽出する必要があります。

製品リストを管理するコンポーネントを考えてみましょう。ユーザーは新しい製品名を追加できます。制約は、製品名が一意である必要があることです。

最初の試みは、製品名のリストのセッターをコンポーネント内に直接保持することでした。

関数 ProductsList() {
    const [names, setNames] = useState([]);  
    定数[newName, setNewName] = useState('');

    const map = name => <div>{name}</div>;

    const handleChange = イベント => setNewName(event.target.value);
    定数handleAdd = () => {    
        const s = new Set([...names, newName]);    
        setNames([...s]); };
    戻る (
        <div className="製品">
            {names.map(マップ)}
            <input type="text" onChange={handleChange} />
            <button onClick={handleAdd}>追加</button>
        </div>
    );
}

names 状態変数には製品名が保持されます。 [追加] ボタンをクリックすると、addNewProduct() イベント ハンドラが呼び出されます。

addNewProduct() 内では、製品名を一意に保つために Set オブジェクトが使用されます。コンポーネントはこの実装の詳細を考慮する必要がありますか?不要。

複雑な状態セッター ロジックをカスタム フックに分離するのが最適です。始めましょう。

各アイテムを一意にするための新しいカスタム フック useUnique():

// ユニークを使用する
エクスポート関数 useUnique(initial) {
    const [items, setItems] = useState(initial);
    const add = newItem => {
        const uniqueItems = [...新しいセット([...items, newItem])];
        ユニークなアイテムを設定します。
    };
    [items, add] を返します。
};

カスタム状態管理をフックに抽出することで、ProductsList コンポーネントはより軽量になります。

'./useUnique' から useUnique をインポートします。

関数 ProductsList() {
  const [names, add] = useUnique([]); const [newName, setNewName] = useState('');

  const map = name => <div>{name}</div>;

  const handleChange = イベント => setNewName(e.target.value);
  const handleAdd = () => add(newName);
  戻る (
    <div className="製品">
      {names.map(マップ)}
      <input type="text" onChange={handleChange} />
      <button onClick={handleAdd}>追加</button>
    </div>
  );
}

const [names, addName] = useUnique([]) はカスタムフックを有効にします。コンポーネントは、複雑な状態管理によって行き詰まることがなくなりました。

リストに新しい名前を追加する場合は、add('New Product Name') を呼び出します。

最も重要なのは、複雑な状態管理をカスタム フックに抽出することの利点です。

  • このコンポーネントには状態管理の詳細は含まれません
  • カスタムフックは再利用できる
  • カスタムフックは簡単に分離してテストできる

No.3 複数状態操作の抽出

複数の状態操作をリデューサーに抽出します。

ProductsList の例を続けて、リストから製品名を削除する「削除」操作を導入しましょう。

ここで、製品の追加と削除という 2 つの操作をコーディングする必要があります。これらの操作を処理することで、簡素化機能を作成し、コンポーネントを状態管理ロジックから解放できます。

繰り返しになりますが、このアプローチは、コンポーネントから複雑な状態管理を抽出するというフックの考え方に適合しています。

以下は、製品を追加および削除するリデューサーの実装の 1 つです。

関数 uniqueReducer(状態, アクション) {
    スイッチ(アクションタイプ){
        ケース '追加':
            [...new Set([...state, action.name])] を返します。
        ケース '削除':
            state.filter(name => name === action.name) を返します。
        デフォルト:
            新しい Error() をスローします。
    }
}

次に、React の useReducer() フックを呼び出して、製品リストで uniqueReducer() を使用できます。

関数 ProductsList() {
    const [names, dispatch] = useReducer(uniqueReducer, []);
    定数[newName, setNewName] = useState('');

    const handleChange = イベント => setNewName(event.target.value);

    const handleAdd = () => dispatch({ type: 'add', name: newName });
    定数マップ = 名前 => {
        const delete = () => dispatch({ type: 'delete', name });    
        戻る (
            <div>
                {名前}
                <button onClick={delete}>削除</button>
            </div>
        );
    }

    戻る (
        <div className="製品">
            {names.map(マップ)}
            <input type="text" onChange={handleChange} />
            <button onClick={handleAdd}>追加</button>
        </div>
    );
}

const [names, dispatch] = useReducer(uniqueReducer, []) は uniqueReducer を有効にします。 names は製品の名前を保持する状態変数であり、dispatch はアクション オブジェクトで呼び出される関数です。

[追加] ボタンをクリックすると、ハンドラーは dispatch({ type: 'add', name: newName }) を呼び出します。追加アクションをディスパッチすると、リデューサー uniqueReducer が新しい製品名を状態に追加します。

同様に、[削除] ボタンをクリックすると、ハンドラーは dispatch({ type: 'delete', name }) を呼び出します。削除操作は、名前の状態から製品名を削除します。

興味深いことに、リデューサーはコマンド パターンの特殊なケースです。

要約する

状態変数は 1 つのポイントのみに焦点を当てる必要があります。

状態に複雑な更新ロジックがある場合は、そのロジックをコンポーネントからカスタム フックに抽出します。

同様に、状態に複数のアクションが必要な場合は、リデューサーを使用してそれらのアクションを組み合わせます。

どのようなルールを使用する場合でも、状態は可能な限り単純かつ分離された状態に保つ必要があります。コンポーネントは状態の更新の詳細を気にする必要はありません。カスタムフックまたはリデューサーの一部にする必要があります。

これら 3 つの簡単なルールにより、状態ロジックの理解、保守、テストが容易になります。

これで、React の状態管理の 3 つのルールに関するこの記事は終了です。React の状態管理に関するその他のコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • React のグローバル状態管理の 3 つの基本メカニズムの調査
  • ReactでVueの状態管理メソッドを使用する例
  • さまざまなReact状態マネージャーの解釈と使用方法

<<:  MySQLが2つのテーブルを関連付ける際のエンコードの問題と解決策

>>:  Linux で crond ツールを使用してスケジュールされたタスクを作成する方法

推薦する

JSはカード配布アニメーションを実現します

この記事の例では、カード配布アニメーションを実装するためのJSの具体的なコードを参考までに共有してい...

Linux での MySQL 5.6.24 (バ​​イナリ) 自動インストール スクリプト

この記事では、Linux環境でのmysql5.6.24自動インストールスクリプトコードを参考までに共...

MySQL 8.0.15 のインストールと設定のグラフィックチュートリアル

この記事ではMySQL 8.0.15のインストールと設定方法を参考までに記録します。具体的な内容は以...

Linux インストール Redis 実装プロセスとエラー解決

今日、redis をインストールしたところ、今までになかったいくつかのエラーが発生しました。ここで記...

W3C チュートリアル (11): W3C DOM アクティビティ

ドキュメント オブジェクト モデル (DOM) は、プログラムがドキュメントのコンテンツ、構造、およ...

JavaScript関数の使い方の詳細な説明

目次1. 関数を宣言する2. 関数の呼び出し3. 関数パラメータ4. 関数の戻り値5. 議論の使用6...

Jenkins を使用した Vue プロジェクトのワンクリック パッケージングと公開の実装

目次Jenkinsのインストールインストールポート番号を変更します(デフォルトのポートは8080です...

JavaScript でアルゴリズムの複雑さを学ぶ方法

目次概要Big O 表記法とは何ですか?オー(1)の上) (n^2) O(logn) ですの上!)結...

JSブラウザイベントモデルの詳細な説明

目次イベントとは簡単な例イベントをバインドする方法フレームワーク内のイベントイベントオブジェクトイベ...

HTML ヘッド構造

以下では、よく使われるヘッド構造と、各タグや要素の意味や使用シーンを紹介します(この記事は、Yisi...

HTML テーブル マークアップ チュートリアル (40): ヘッダーの暗い境界線の色属性 BORDERCOLORDARK

テーブルヘッダーでは、暗い境界線の色を個別に定義できます。基本的な構文<TH 境界線の色を暗く...

MySQL インデックス使用状況監視スキル (収集する価値あり!)

概要リレーショナル データベースでは、インデックスは、データベース テーブル内の 1 つ以上の列の値...

フラッシュプラグインを使用してPCのカメラを呼び出し、TMLページに埋め込む方法

序文この記事を書いた主な理由は、チームリーダーが、ブラウザを使用してコンピューターのカメラを呼び出し...

プロジェクトにおけるVue3のロジック抽出とフィールド表示についての簡単な説明

目次論理階層化異なる地域から事業を分離するこれを実行する利点このようなシナリオにどう対処するか最適化...

Nginx で WordPress 擬似静的を設定する方法の例

Baidu の擬似静的の説明を引用します。擬似静的は、実際の静的に相対的です。通常、検索エンジンの使...