web-dev-qa-db-ja.com

Reactフックを使用するcomponentDidUpdateと同等

tldr;componentDidUpdateをシミュレートする方法、または配列でkeyを使用してコンポーネントを強制的にリセットするにはどうすればよいですか?

タイマーを表示し、ゼロに達するとコールバックを実行するコンポーネントを実装しています。その目的は、コールバックがオブジェクトのリストを更新することです。後者のコンポーネントは、新しい ReactフックuseStateおよびuseEffectで構成されています。

stateには、タイマーが開始された時刻と残りの時刻への参照が含まれます。 effectは、残りの時間を更新し、コールバックを呼び出す必要があるかどうかを確認するために、毎秒呼び出される間隔を設定します。

コンポーネントは、タイマーを再スケジュールしたり、ゼロに達したときに間隔を維持したりするためのものではなく、コールバックを実行してアイドル状態にする必要があります。タイマーを更新するために、配列をkeyに渡してコンポーネントの状態をリセットし、タイマーを再起動することを望んでいました。残念ながら、keyは文字列と共に使用する必要があるため、配列の参照が変更されたかどうかは効果がありません。

また、懸念している配列を渡すことで、小道具に変更をプッシュしようとしましたが、状態は維持されたため、間隔はリセットされませんでした。

新しいフックAPIのみを使用して状態を強制的に更新するために、配列の浅い変化を観察するための好ましい方法は何でしょうか?

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';

function getTimeRemaining(startedAt, delay) {
    const now = new Date();
    const end = new Date(startedAt.getTime() + delay);
    return Math.max(0, end.getTime() - now.getTime());
}

function RefresherTimer(props) {
    const [startedAt, setStartedAt] = useState(new Date());
    const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));

    useEffect(() => {

        if (timeRemaining <= 0) {
            // The component is set to idle, we do not set the interval.
            return;
        }

        // Set the interval to refresh the component every second.
        const i = setInterval(() => {
            const nowRemaining = getTimeRemaining(startedAt, props.delay);
            setTimeRemaining(nowRemaining);

            if (nowRemaining <= 0) {
                props.callback();
                clearInterval(i);
            }
        }, 1000);

        return () => {
            clearInterval(i);
        };
    });

    let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
    if (timeRemaining <= 0) {
        message = 'Refreshing now...';
    }

    return <div>{message}</div>;
}

RefresherTimer.propTypes = {
    callback: PropTypes.func.isRequired,
    delay: PropTypes.number
};

RefresherTimer.defaultProps = {
    delay: 2000
};

export default RefresherTimer;

keyで使用しようとしました:

<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />

小道具の変更で使用しようとしました:

<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />

listOfObjectsはオブジェクトの配列を指します。オブジェクト自体は必ずしも変更されないため、配列を!==と比較する必要があります。通常、値はReduxから取得されます。アクションupdateListOfObjectsは、newListOfObjects = [...listOfObjects]のように配列を再初期化します。

20
FMCorz

useRefは、機能コンポーネントに「インスタンス変数」を作成します。状態を更新せずにマウントまたは更新フェーズにあるかどうかを示すフラグとして機能します。

const mounted = useRef();
useEffect(() => {
  if (!mounted.current) {
    mounted.current = true;
  } else {
    // do componentDidUpate logic
  }
});
21
Morgan Cheng

つまり、配列の参照が変更されたときにタイマーをリセットしたいのですよね?その場合、何らかの差分メカニズムを使用する必要があります。純粋なフックベースのソリューションは、次のようにuseEffectの2番目のパラメーターを利用します。

function RefresherTimer(props) {
  const [startedAt, setStartedAt] = useState(new Date());
  const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));

  //reset part, lets just set startedAt to now
  useEffect(() => setStartedAt(new Date()),
    //important part
    [props.listOfObjects] // <= means: run this effect only if any variable
    // in that array is different from the last run
  )

  useEffect(() => {
    // everything with intervals, and the render
  })
}

この動作の詳細はこちら https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

3
Bear-Foot

コンポーネントを再マウントする方法は、新しいkeyプロパティを提供することです。文字列である必要はありませんが、内部的に文字列に強制変換されるため、listOfObjectsが文字列である場合、keyが内部でlistOfObjects.toString()と比較されます。

任意のランダムキーを使用できます。 uuidまたはMath.random()。親コンポーネントでlistOfObjectsの浅い比較を実行して、新しいキーを提供できます。 useMemoフックは、再マウントキーを条件付きで更新するために親状態で使用できます。また、listOfObjectsは、メモする必要があるパラメーターのリストとして使用できます。 です:

  const remountKey = useMemo(() => Math.random(), listOfObjects);

  return (
    <div>
      <RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
    </div>
  );

キーを再マウントする代わりに、子コンポーネントは自身の状態をリセットし、コールバックを公開してリセットをトリガーできます。

子コンポーネント内でlistOfObjectsの浅い比較を行うと、親コンポーネントの実装を認識する必要があるため、アンチパターンになります。

0
Estus Flask