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

推薦する

React仮想リストの実装

目次1. 背景2. バーチャルリストとは何か3. 関連概念の紹介4. 仮想リストの実装4.1 ドライ...

Tomcat サーバーの設定と Web プロジェクトの公開に関する IDEA グラフィック チュートリアル

1. Webプロジェクトを作成したら、Tomcatを例にサーバーを構成する必要があります。 2. 実...

Celery と Docker を使用して Django で定期的なタスクを処理する方法

Django アプリケーションを構築して拡張していくと、必然的に特定のタスクをバックグラウンドで自動...

6秒でMySQLに100万件のレコードを挿入する方法を教えます

1. アイデアMySQL に 1,000,000 件のレコードを挿入するのにたった 6 秒しかかかり...

MySQL データ分析ストレージエンジンの例の説明

目次1. 事例紹介2. システムのデフォルトのストレージエンジンとデフォルトの文字セットを表示する3...

MySQL オンラインリカバリ UNDO テーブルスペース 実戦記録

1 MySQL5.6 1.1 関連パラメータMySQL 5.6 では、innodb_undo_dir...

React双方向データバインディングの原理についての簡単な説明

目次双方向データバインディングとは双方向データバインディングの実装データ影響ビュービューはデータに影...

Linux lessコマンド例の詳細な説明

ファイル名が少ないファイルを表示ファイル名を少なく | grep -n コンテンツを検索内容に応じて...

nginx で第 3 レベルドメイン名を設定する方法の例

問題の説明nginx を設定することで、異なるポートを介して異なる Web アプリケーションにアクセ...

Vue フィルターの実装と適用シナリオの詳細な説明

1. 簡単な紹介Vue.js を使用すると、一般的なテキストの書式設定に使用できる独自のフィルターを...

Vue で動的なスタイルを実現するためのさまざまな方法のまとめ

目次1. 三項演算子の判定2. 動的に設定されるクラス3. 方法判定4. 配列バインディング5. e...

js の getBoundingClientRect() メソッドの詳細な説明

1. getBoundingClientRect() 分析getBoundingClientRect...

Webサービスのリモートデバッグとタイムアウト動作原理の分析

Webサービスのリモートデバッグ.NET では、WEBSERVICE のリモート デバッグ機能はデフ...

MySQL カーソル関数と使用法

目次意味カーソルの役割カーソルの使用カーソルの宣言カーソルを開くカーソルデータのトラバースカーソルを...

MySQL データベース開発の 36 の原則 (要約)

序文これらの原則は実際の戦闘から要約されています。あらゆる原則の背後には血なまぐさい教訓があるこれら...