web-dev-qa-db-ja.com

反応フックとsetInterval

反応フックを使用してカルーセルに自動ネクスト(数秒後)を実装するために、バックグラウンドで「クロック」を保持するだけの代替手段はありますか?

以下のカスタム反応フックは、カルーセルの現在の(アクティブ)インデックスを変更するための手動(次へ、前へ、リセット)および自動(開始、停止)メソッドをサポートするカルーセルの状態を実装します。

const useCarousel = (items = []) => {
  const [current, setCurrent] = useState(
    items && items.length > 0 ? 0 : undefined
  );

  const [auto, setAuto] = useState(false);

  const next = () => setCurrent((current + 1) % items.length);
  const prev = () => setCurrent(current ? current - 1 : items.length - 1);
  const reset = () => setCurrent(0);
  const start = _ => setAuto(true);
  const stop = _ => setAuto(false);


useEffect(() => {
    const interval = setInterval(_ => {
      if (auto) {
        next();
      } else {
        // do nothing
      }
    }, 3000);
    return _ => clearInterval(interval);
  });

  return {
    current,
    next,
    prev,
    reset,
    start,
    stop
  };
};
6
marco alves

setIntervalsetTimeoutの違いは、コンポーネントが再表示されるときに常にタイマーを再起動することで失われたくない場合があります。 このフィドル は、2つのドリフトの違いを示します(そして、Reactが実行しているすべての計算を考慮に入れていませんもう少し)。

マルコの your answer を参照すると、setIntervalの使用は完全に失われています。条件のないエフェクトは、コンポーネントが再レンダリングされるたびに破棄され、再実行されるためです。したがって、最初の例では、current依存関係を使用すると、currentが変更されるたびに(間隔が実行されるたびに)その効果が破棄され、再実行されます。 2つ目も同じことを行いますが、実際には状態が変化するたびに(再レンダリングの原因)、予期しない動作が発生する可能性があります。機能する唯一の理由は、next()によって状態が変化するためです。

exactタイミングにおそらく関係がないという事実を考慮すると、setTimeoutおよびcurrent変数を依存関係として使用して、autoを単純な方法で使用するのが最もクリーンです。 。答えの一部を言い直すには、次のようにします。

useEffect(
  () => {
    if (!auto) return;
    const interval = setInterval(_ => {
      next();
    }, autoInterval);
    return _ => clearInterval(interval);
  },
  [auto, current]
);

currentの依存関係をnextで置き換えると、useEffect内で行っていることをより直接的に表すことができるので、注意してください。しかし、Reactがこれらの依存関係をどのように差分するかは100%わかりません。そのため、そのままにしておきます。

一般的に、この答えを読んでいて単純なタイマーを実行する方法が必要な場合は、OPの元のコードを考慮しないバージョンもあり、タイマーを個別に開始および停止する方法も必要ありません。

const [counter, setCounter] = useState(0);
useEffect(
  () => {
    const id= setTimeout(() => {
      setCounter(counter + 1);
    }, 1000);
    return () => {
      clearTimer(id);
    };
  },
  [counter],
);

ただし、setTimeoutsetIntervalよりもドリフトが大きいという事実を考えると、より正確な間隔を使用する方法について疑問に思うかもしれません。ここでも、OPのコードを使用しない一般的な方法を1つ示します。

const [counter, setCounter] = useState(30);
const r = useRef(null);
r.current = { counter, setCounter };
useEffect(
  () => {
    const id = setInterval(() => {
      r.current.setCounter(r.current.counter + 1);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  },
  ['once'],
);

ここで何が起きてるの?さて、setIntervalのコールバックを取得して常に現在受け入れ可能なバージョンのsetCounterを参照するには、いくつかの変更可能な状態が必要です。 Reactは、useRefでこれを提供します。useRef関数は、currentプロパティを持つオブジェクトを返します。次に、そのプロパティ(コンポーネントが再レンダリングされるたびに発生します)を現在のcounterおよびsetCounterのバージョン。

次に、レンダリングごとに間隔が破棄されないようにするために、変化しないことが保証されているuseEffectに依存関係を追加します。私の場合、文字列"once"この効果を一度だけ設定するように強制していることを示します。コンポーネントがマウント解除されても、間隔は破棄されます。

したがって、OPの元の質問に私たちが知っていることを適用すると、次のようにsetIntervalを使用してドリフトの可能性が低いスライドショーを作成できます。

// ... OP's implementation code including `autoInterval`,
// `auto`, and `next` goes above here ...

const r = useRef(null);
r.current = { next };
useEffect(
  () => {
    if (!auto) return;
    const id = setInterval(() => {
      r.current.next();
    }, autoInterval);
    return () => {
      clearInterval(id);
    };
  },
  [auto],
);
3
Don

current値は、実行中である必要がある限り、「間隔」ごとに変更されるため、コードが開始され、すべてのレンダリングで新しいタイマーを停止します。あなたはここで実際にこれを見ることができます:

https://codesandbox.io/s/03xkkyj19w

setIntervalsetTimeoutに変更すると、まったく同じ動作が得られます。 setTimeoutは永続的なクロックではありませんが、どちらもとにかくクリーンアップされるため、問題ではありません。

タイマーをまったく開始したくない場合は、setIntervalの前ではなく、その前に条件を置きます。

  useEffect(
    () => {
      let id;

      if (run) {
        id = setInterval(() => {
          setValue(value + 1)
        }, 1000);
      }

      return () => {
        if (id) {
          alert(id) // notice this runs on every render and is different every time
          clearInterval(id);
        }
      };
    }
  );
1
azium

これまでのところ、以下の両方のソリューションは期待どおりに機能するようです:

条件付きでタイマーを作成する —これは、useEffectがautocurrentの両方に依存していることが必要です

useEffect(
    () => {
      if (!auto) return;
      const interval = setInterval(_ => {
        next();
      }, autoInterval);
      return _ => clearInterval(interval);
    },
    [auto, current]
  );

状態への更新を条件付きで実行 — useEffect依存関係は必要ありません

useEffect(() => {
    const interval = setInterval(_ => {
      if (auto) {
        next();
      } else {
        // do nothing
      }
    }, autoInterval);
    return _ => clearInterval(interval);
  });

setIntervalsetTimeoutに置き換えると、両方のソリューションが機能します

0
marco alves

指定したミリ秒数後にuseTimeoutを返すtrueフックを使用できます。

0
Vad