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 for ループで setTimeout を使用する 4 つのソリューション

目次概要解決策 1: クロージャ解決策2: 構造を分割する解決策3:解決策4: setTimeout...

Web デザインでフラッシュ オーバーレイ ポップアップ レイヤーの z-index プロパティを設定しても機能しない

デフォルトでは、Flash は常にページのトップレベルに表示されます。つまり、ページに DHTML ...

MySQL 外部キー設定方法の例

1. 外部キーの設定方法1. MySQL では、2 つのテーブルを関連付けるために、外部キー (FO...

JS 非同期実行の原則とコールバックの詳細

1. JSの非同期実行の原則JavaScript はシングルスレッドですが、ブラウザはマルチスレッド...

Mysql テーブルで利用可能な最小 ID 値を照会する方法

今日、研究室のプロジェクトを見ていたとき、私にとって「難しい」問題に遭遇しました。実は、それは私があ...

MySQL 5.7.9 シャットダウン構文例の詳細な説明

mysql-5.7.9 では、ついにシャットダウン構文が提供されます。以前は、MySQL データベー...

Docker プライベートリポジトリの管理とローカルリポジトリ内のイメージの削除

1: Dockerプライベートウェアハウスのインストール1. イメージリポジトリからイメージをダウン...

element-plus でオンデマンドインポートとグローバルインポートを実装する方法

目次オンデマンドインポート:グローバルインポートオンデマンドインポート:プラグインをインストールする...

Linux での grep コマンドの使い方の詳細な説明

Linux grep コマンドLinux の grep コマンドは、ファイル内の条件を満たす文字列を...

Linuxはiptablesを使用して複数のIPからのサーバーへのアクセスを制限します

序文Linux カーネルでは、netfilter は、パケット フィルタリング、ネットワーク アドレ...

WeChatミニプログラムがシームレスなスクロールを実現

この記事の例では、WeChatアプレットのシームレスなスクロールを実現するための具体的なコードを参考...

MySQLオンラインデッドロック分析練習

序文MySQL を学習する際に、MySQL のロック メカニズムについて簡単に理解したことがあると思...

JavaScript プロトタイプチェーンを理解するための 2 つの図

目次1. プロトタイプの関係2. プロトタイプチェーン3. 結論序文:前回の記事では、JavaScr...

Mysql varchar型の合計操作例

友人の中には、データベースについて学習しているときに、テーブル構造を作成するときに誤ってフィールドを...