React Hooks に基づく小さな状態管理の詳細な説明

React Hooks に基づく小さな状態管理の詳細な説明

この記事では、主に React Hooks に基づく状態共有ソリューションを紹介し、その実装を紹介し、使用経験をまとめ、状態管理の追加オプションを提供することを目的としています。

React Hooks に基づく状態共有の実装

React コンポーネント間で状態を共有することは一般的な問題であり、Redux、MobX など多くの解決策があります。これらのソリューションは非常に専門的で、長年の実績がありますが、個人的には、それほど複雑でないプロジェクトには適しておらず、追加の複雑さをもたらす可能性があると思います。

実際、多くの場合、ミューテーションやアクションを定義したり、コンテキストのレイヤーを使用したり、connect や mapStateToProps を記述したりしたくありません。必要なのは、参照や使用が簡単な軽量でシンプルな状態共有ソリューションです。

Hooks の誕生と人気により、私のアイデアは実現しました。

次に、私が現在使用しているソリューションを紹介します。Hooksとパブリッシュ/サブスクライブモデルを組み合わせることで、シンプルで実用的な状態共有ソリューションを実装できます。コードがあまり多くないため、完全な実装を以下に示します。

輸入 {
  急送、
  状態設定アクション、
  コールバックの使用、
  使用効果、
  使用Reducer、
  使用Ref、
  使用状態、
} を 'react' から取得します。

/**
 * https://github.com/facebook/react/blob/bb88ce95a87934a655ef842af776c164391131ac/packages/shared/objectIs.js を参照してください
 * Object.isポリフィルをインライン化して、消費者が独自のポリフィルを出荷する必要を回避しました。
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
関数は(x: 任意、y: 任意): ブール値 {
  戻り値: (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);
}

const objectIs = typeof Object.is === 'function' ? Object.is : is;

/**
 * https://github.com/facebook/react/blob/933880b4544a83ce54c8a47f348effe725a58843/packages/shared/shallowEqual.js を参照してください
 * オブジェクトのキーを反復処理してfalseを返すことで等価性を実行します
 * いずれかのキーの値が引数間で厳密に等しくない場合。
 * すべてのキーの値が厳密に等しい場合は true を返します。
 */
関数 shallowEqual(objA: any, objB: any): ブール値 {
  (objA, objB) の場合
    true を返します。
  }

  もし (
    objA の型 !== 'オブジェクト' ||
    objA === null ||
    objB の型 !== 'オブジェクト' ||
    objB === null
  ){
    false を返します。
  }

  定数 keysA = Object.keys(objA);
  定数 keysB = Object.keys(objB);

  (keysAの長さ!==keysBの長さ)の場合{
    false を返します。
  }

  // A のキーが B と異なるかどうかをテストします。
  (i = 0 とします; i < keysA.length; i++) {
    もし (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ){
      false を返します。
    }
  }

  true を返します。
}

const useForceUpdate = () => useReducer(() => ({}), {})[1] を VoidFunction として;

型 ISubscriber<T> = (前の状態: T、次の状態: T) => void;

エクスポートインターフェースISharedState<T> {
  /** データを静的に取得します。非コンポーネントでの使用や、データがビューにバインドされていない場合に適しています*/
  取得: () => T;
  /** データを変更し、新しい値を割り当てる*/
  設定: Dispatch<SetStateAction<T>>;
  /** (浅い) マージ更新データ*/
  更新: Dispatch<Partial<T>>;
  /** フックメソッドは、コンポーネントで使用するのに適したデータを取得します。データが変更されると、コンポーネントは自動的に再レン​​ダリングされます*/
  使用: () => T;
  /** サブスクリプションデータの変更 */
  サブスクライブ: (cb: ISubscriber<T>) => () => void;
  /** データの変更を購読解除する */
  登録解除: (cb: ISubscriber<T>) => void;
  /** いくつかの状態を除外する */
  usePick<R>(ピッカー: (状態: T) => R, 依存関係: readonly any[]): R;
}

エクスポート型 IReadonlyState<T> = Omit<ISharedState<T>, 'set' | 'update'>;

/**
 * 異なるインスタンス間で共有できる状態を作成します * @param initialState 初期データ */
エクスポートconst createSharedState = <T>(initialState: T): ISharedState<T> => {
  状態を initialState とします。
  定数サブスクライバー: ISubscriber<T>[] = [];

  //状態の変更をサブスクライブする const subscribe = (subscriber: ISubscriber<T>) => {
    subscribers.push(サブスクライバー);
    return () => 購読を解除します(購読者);
  };

  // 状態変更の購読を解除する const unsubscribe = (subscriber: ISubscriber<T>) => {
    定数インデックス = subscribers.indexOf(subscriber);
    インデックス > -1 && subscribers.splice(index, 1);
  };

  // 最新の状態を取得する
  const get = () => 状態;

  // 状態を変更する
  const set = (次: SetStateAction<T>) => {
    定数 prevState = 状態;
    // @ts を無視
    const nextState = typeof next === 'function' ? next(prevState): next;
    オブジェクトが状態、次の状態である場合
      戻る;
    }
    状態 = 次の状態;
    サブスクライバー.forEach((cb) => cb(前の状態、状態));
  };

  //フックの使用状況の最新状態を取得する const use = () => {
    定数 forceUpdate = useForceUpdate();

    使用効果(() => {
      isMounted = true とします。
      // 最初に更新されたデータを使用できないことを避けるために、マウント後すぐにコンポーネントを更新します。forceUpdate();
      定数un = subscribe(() => {
        if (!isMounted) 戻り値:
        強制更新();
      });
      戻り値 () => {
        国連();
        isMounted = false;
      };
    }, []);

    状態を返します。
  };

  const usePick = <R>(ピッカー: (s: T) => R, deps = []) => {
    定数ref = useRef<any>({});

    ref.current.picker = ピッカー;

    const [pickedState, setPickedState] = useState<R>(() =>
      ref.current.picker(状態)、
    );

    ref.current.oldState = 選択された状態;

    定数sub = useCallback(() => {
      const selectedOld = ref.current.oldState;
      const selectedNew = ref.current.picker(state);
      if (!shallowEqual(選択された古いもの、選択された新しいもの)) {
        //pickedNew が関数にならないようにする
        選択された新しい状態を設定します。
      }
    }, []);

    使用効果(() => {
      定数 un = subscribe(sub);
      un を返します。
    }, []);

    使用効果(() => {
      サブ();
    }, [...依存関係]);

    選択された状態を返します。
  };

  戻る {
    得る、
    セット、
    更新: (入力: Partial<T>) => {
      設定((pre) => ({
        ...前、
        ...入力、
      }));
    },
    使用、
    購読する、
    登録解除、
    ピックを使用する、
  };
};

createSharedState を使用すると、次のステップは共有状態を簡単に作成することであり、コンポーネント内でそれを使用する方法も非常に直接的です。

// 状態インスタンスを作成します。const countState = createSharedState(0);

定数A = () => {
  // コンポーネント内のフックを使用してレスポンシブなデータを取得します const count = countState.use();
  <div>A: {count}</div> を返します。
};

定数B = () => {
  // データを変更するには set メソッドを使用します return <button onClick={() => countState.set(count + 1)}>Add</button>;
};

定数C = () => {
  戻る (
    <ボタン
      クリックすると{() => {
        // get メソッドを使用してデータを取得します console.log(countState.get());
      }}
    >
      得る
    </ボタン>
  );
};

定数App = () => {
  戻る (
    <>
      <あ />
      <B />
      <C />
    </>
  );
};

複雑なオブジェクトの場合、他のフィールドの変更によって発生する冗長なレンダリングを回避するために、コンポーネント内の指定された部分のデータの変更を監視するメソッドも提供されます。

const 複合状態 = 共有状態を作成します({
  : 0,
  b: {
    c: 0,
  },
});

定数A = () => {
  const a = complexState.usePick((state) => state.a);
  <div>A: {a} を返します。
};

ただし、複雑なオブジェクトの場合は、通常、複数の単純な状態から複雑なオブジェクトを派生する複合派生アプローチを使用することをお勧めします。また、元のデータに基づいた計算結果が必要になる場合もあるので、ここではデータを導出する方法も提供されています。

依存関係を明示的に宣言することで、データ ソースをリッスンし、それを計算関数に渡して、応答性の高い派生結果を取得できます。

/**
 * 導出された(または計算された)状態
 * ```ts
 * const count1 = createSharedState(1);
 * const count2 = createSharedState(2);
 * const count3 = createDerivedState([count1, count2], ([n1, n2]) => n1 + n2);
 * ```
 * @param ストア
 * @param fn
 * @param 初期値
 * @戻り値
 */
エクスポート関数createDerivedState<T = any>(
  ストア: IReadonlyState<any>[]、
  fn: (値: any[]) => T,
  オプション: {
    /**
     * 同期的に応答するかどうか * @default false
     */
    同期?: ブール値;
  },
): IReadonlyState<T> & {
  停止: () => void;
} {
  const { sync } = { sync: false, ...opts };
  値を: any[] = stores.map((it) => it.get());
  const innerModel = createSharedState<T>(fn(values));

  promise を次のように記述します: Promise<void> | null = null;

  const uns = ストア.map((it, i) => {
    戻ります。subscribe((_old, newValue) => {
      値[i] = 新しい値;

      (同期)の場合{
        innerModel.set(() => fn(values));
        戻る;
      }

      // 非同期更新プロミス =
        約束
        Promise.resolve().then(() => {
          innerModel.set(() => fn(values));
          プロミス = null;
        });
    });
  });

  戻る {
    取得: innerModel.get、
    使用: innerModel.use、
    サブスクライブ: innerModel.subscribe、
    登録解除: innerModel.unsubscribe、
    usePick: innerModel.usePick、
    停止: () => {
      uns.forEach((un) => un());
    },
  };
}

以上で、Hooks による状態共有方式の実装の紹介は終了です。

最近のプロジェクトでは、状態共有が必要なシナリオがあり、上記の方法を選択しました。Webプロジェクトでも、小規模なプログラムのTaroプロジェクトでも同じ実装セットを使用でき、比較的スムーズに進んでいます。

ユーザーエクスペリエンス

最後に、このアプローチのいくつかの特徴をまとめてみましょう。

1. 他の概念を導入せず、フックに基づくパブリッシュ/サブスクライブ モデルとのみ組み合わせたシンプルな実装で、Taro などの React のようなシナリオで使用できます。

2. 使いやすい。他の概念がないため、create メソッドを直接呼び出すことで状態の参照を取得し、状態インスタンスで use メソッドを呼び出してコンポーネントとデータのバインディングを完了できます。

3. 型フレンドリー。状態を作成するときに追加の型を定義する必要はなく、使用時に型が自動的に推測されます。

4. 状態の参照は一定であり、最新の値は常に状態の get メソッドを通じて取得できるため、フックの「クロージャ トラップ」を回避します。

定数 countState = createSharedState(0);

定数App = () => {
  使用効果(() => {
    間隔を設定する(() => {
      コンソールにログ出力します。
    }, 1000);
  }, []);
  // 戻る ...
};

5. 複数の React アプリケーション間の共有を直接サポートします。ポップアップ ボックスを使用する場合、複数の React アプリケーションを使用する方が簡単です。

定数 countState = createSharedState(0);

constコンテンツ = () => {
  countState を定数で指定します。
  <div>{count}</div> を返します。
};

定数A = () => (
  <ボタン
    クリックすると{() => {
      ダイアログ.info({
        タイトル: 「アラート」
        コンテンツ: <コンテンツ />,
      });
    }}
  >
    開ける
  </ボタン>
);

6. コンポーネント外のシーンでのデータの取得/更新をサポート

7. SSRシナリオには大きな制限があります。状態は断片化され分散化された方法で作成され、状態のライフサイクルはReactアプリケーションに従わないため、SSRアプリケーションコードを同型的に記述することは不可能です。

上記がこの記事の全内容です。実際、Hooks は長い間人気があり、コミュニティにはすでに多くの新しい状態共有実装が存在します。これは参考としてのみ使用されます。

上記の特徴から、この方法には明らかな利点と致命的な欠点(SSR の場合)がありますが、実際の使用では、具体的な状況に応じて適切な方法を選択できます。たとえば、Taro2 のアプレット アプリケーションでは SSR を気にする必要がないため、このアプローチを好みます。SSR 同型プロジェクトの場合は、Redux を選択する必要があります。

つまり、もう 1 つのオプションがあり、それをどのように使用するかは具体的な状況によって異なります。

以上がReact Hooksによるスモールステート管理の詳しい解説内容です。React Hooksのスモールステート管理についてさらに詳しく知りたい方は、123WORDPRESS.COMの他の関連記事もぜひご覧ください!

以下もご興味があるかもしれません:
  • Reactはフックを使用して、制御されたコンポーネントの状態バインディングを簡素化します。
  • Reactでカスタムフックを作成する方法を教えます
  • React Hooksの詳細な説明
  • React Hooksを使用する際のよくある落とし穴
  • Reactフックの長所と短所
  • React Hooks の一般的な使用シナリオ (概要)
  • Reactフック入門チュートリアル
  • 反応フックの使い方の詳細な説明

<<:  CSS ファンタスティックボーダーアニメーション効果の実装

>>:  MySQL データベースの集計クエリと結合クエリ操作

推薦する

JavaScript を使用してページに動的な検証コードを実装する例

導入:現在、プログラム攻撃を防ぐために、ユーザーがログインまたは登録するときに多くの動的検証テクノロ...

Vueメソッドに基づくシンプルなタイマーの実装

Vueのシンプルなタイマーを参考にしてください。具体的な内容は以下のとおりです原理: setInte...

Vue再帰コンポーネントの簡単な使用例

序文多くの学生は既に再帰に精通していると思います。アルゴリズムの問​​題を解決するために再帰がよく使...

JSはショッピングカート効果の単純な加算と減算を実装します

この記事の例では、ショッピングカートの簡単な追加と削除を実現するためのJSの具体的なコードを参考まで...

Linux で rpm パッケージを見つけるために CD をマウントする方法

前面に書かれたLinux を使用する際にソフトウェアをインストールする必要がある場合があります。もち...

CSS3 のフレックスレイアウト幅の無効性の解決策

2 列レイアウトはプロジェクトでよく使用されます。この効果を実現する方法はたくさんあります。 しかし...

この記事ではSQL CASE WHENの使い方を詳しく説明します

目次シンプルな CASEWHEN 関数:これは、CASEWHEN 条件式関数を使用するのと同じです。...

Nginx セッション損失問題の解決策

nginx をリバース プロキシ tomcat として使用する場合、セッション損失が発生する可能性が...

コンパイル/サーバーなしでブラウザにCommonJSモジュールを実装する

目次導入1. one-click.jsとは2. パッケージングツールはどのように機能しますか? 3....

docker-compose を使用して Clickhouse をすばやくデプロイする方法のチュートリアル

ClickHouse は、オープンソースの列指向 DBMS (Yandex によって開発) です。 ...

MySQLインデックスの失敗の典型的なケース

目次典型的なケース付録: 一般的なインデックス障害の状況典型的なケース次の構造を持つ 2 つのテーブ...

MySQLの指定順序ソートクエリについての簡単な説明

最近、空港や駅でフライト情報を表示するものと似た大型スクリーンディスプレイのプロジェクトに取り組んで...

base target="" はリンクのターゲットオープンフレームを制御します

<base target=_blank> は、基本リンクのターゲット フレームを新しいペ...

Nginx ローカル ディレクトリ マッピング実装コード例

他のデバイスの画像をローカルディレクトリにマウントするなど、サーバー上の静的リソースにアクセスする必...

Linuxで静的ネットワーク接続を構成する方法

Linux システムのネットワーク接続を構成するのは難しい場合があります。幸いなことに、多くの新しい...