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 ツールを使用してスケジュールされたタスクを作成する方法

推薦する

ウェブページに埋め込まれた Flash と IE、FF、Maxthon の互換性の問題

いろいろ苦労した後、インターネットで検索したり、以前の会社のプロジェクトを探したり、他の人のプロジェ...

Vue サーバーに js 構成ファイルをインポートする方法

目次背景成し遂げるvue-cli2.0での設定方法の補足要約する背景プロジェクトにはローカル構成ファ...

explainコマンドがMySQLデータを変更する理由

クエリで EXPLAIN を実行するとデータベースが変更されるかどうかを尋ねられた場合、おそらく「い...

Docker イメージのデフォルトの保存場所を変更する方法 (ソリューション)

システムの初期のパーティション分割により、オペレーティング システム内の対応する / パーティション...

Vueは要素ツリーコントロールを通じてツリーテーブルを実装します

目次実装効果図依存関係をインストールするカスタムツリーコントロールその他の実装要約するVueでは、要...

コピー&ペーストはパッケージングの敵です

OO、デザイン パターン、および多くのオブジェクト指向の原則について話す前に、まず 1 つのことを習...

JSで画面録画機能を作成する

OBS studioかっこいいですが、 JavaScriptもっとかっこいいです。では、 JavaS...

Vueでaxiosをカプセル化する方法

目次1. インストール1. はじめに3. インターフェースルートアドレス4. 使用例4.1 ダウンロ...

Vuex はシンプルなショッピングカート機能を実装します

この記事の例では、ショッピングカート機能を実装するためのvuexの具体的なコードを参考までに共有して...

JS でオブジェクトを作成する 4 つの方法

目次1. リテラル値でオブジェクトを作成する2. 新しいキャラクターを使ってオブジェクトを作成する3...

MySQL インデックスのカーディナリティの概念と使用例

この記事では、例を使用して、MySQL インデックス カーディナリティの概念と使用方法を説明します。...

HTML thead タグの定義と使用法の詳細な紹介

コードをコピーコードは次のとおりです。 <thead> <!– 最初の 2 行をヘ...

MySQLの読み書き分離により挿入後にデータが選択されなくなる問題を解決

MySQLは独立した書き込み分離を設定します。コードに次のものを書くと問題が発生する可能性があります...

threejs でリアルタイムポリゴン屈折を実装する方法

目次序文ステップ1: セットアップと前方屈折ステップ2: 反射とフレネル方程式ステップ3: 多面屈折...

Docker 環境での Jmeter の分散操作に関する詳細なチュートリアル

1. jmeterの基本イメージを構築するDockerfile は次のとおりです。 # Java 8...