web-dev-qa-db-ja.com

Reactフックでスロットルまたはデバウンスを使用する方法?

私は、機能コンポーネントでlodashのthrottleメソッドを使用しようとしています。例:

const App = () => {
  const [value, setValue] = useState(0)
  useEffect(throttle(() => console.log(value), 1000), [value])
  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

useEffect内のメソッドはレンダリングごとに再宣言されるため、スロットル効果は機能しません。

誰かが簡単な解決策を持っていますか?

36
Alexandre Annic

しばらくしてから、setTimeout/clearTimeoutを使用して独自の方法で処理すること(およびそれを別のカスタムフックに移動すること)は、関数ヘルパーを使用するよりもはるかに簡単です。後で処理すると、依存関係の変更により再作成できるuseCallbackにそれを適用した直後に追加の課題が作成されますが、実行の遅延をリセットしたくありません。

以下の元の回答

useRef レンダリング間で値を保存することができます(おそらく必要となるでしょう)。それがそうであるように タイマーに推奨

そんな感じ

const App = () => {
  const [value, setValue] = useState(0)
  const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))

  useEffect(() => throttled.current(value), [value])

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

useCallbackと同様

それはあまりにも働くかもしれません

const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);

しかし、valueが変更されたときにコールバックを再作成しようとすると、次のようになります。

const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);

実行が遅れない場合があります。valueが変更されると、コールバックがすぐに再作成されて実行されます。

そのため、実行が遅れた場合のuseCallbackは、大きな利点をもたらさないと思います。それはあなた次第です。

[UPD]最初は

  const throttled = useRef(throttle(() => console.log(value), 1000))

  useEffect(throttled.current, [value])

しかし、そのようにthrottled.currentはクロージャによって初期value(of 0)にバインドしています。そのため、次のレンダリングでも変更されることはありません。

したがって、クロージャ機能のため、関数をuseRefにプッシュするときは注意してください。

45
skyboyer

次のような小さなカスタムフックである可能性があります。

useDebounce.js

import React, { useState, useEffect } from 'react';

export default (value, timeout) => {
    const [state, setState] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => setState(value), timeout);

        return () => clearTimeout(handler);
    }, [value]);

    return state;
}

使用例:

import React, { useEffect } from 'react';

import useDebounce from '/path/to/useDebounce';

const App = (props) => {
    const [state, setState] = useState({title: ''});    
    const debouncedTitle = useDebounce(state.title, 1000);

    useEffect(() => {
        // do whatever you want with state.title/debouncedTitle
    }, [debouncedTitle]);        

    return (
        // ...
    );
}
// ...

更新:ご存知のように、useEffectは常に初期レンダリングで実行されます。そのため、私の答えを使用すると、おそらくコンポーネントのレンダーが2回実行されることを確認してください。心配する必要はありません。別のカスタムフックを記述するだけです。チェックアウト 私の他の答え 詳細については。

2
Mehdi Dehghani

このユースケースに2つの単純なフック( se-throttled-effectse-debounced-effect )を書いたので、単純なソリューションを探している他の人に役立つかもしれません。

import React, { useState } from 'react';
import useThrottledEffect  from 'use-throttled-effect';

export default function Input() {
  const [count, setCount] = useState(0);

  useEffect(()=>{
    const interval = setInterval(() => setCount(count=>count+1) ,100);
    return ()=>clearInterval(interval);
  },[])

  useThrottledEffect(()=>{
    console.log(count);     
  }, 1000 ,[count]);

  return (
    {count}
  );
}
0
Saman Mohamadi

私はこのようなものを使用し、それはうまくいきます:

let debouncer = debounce(
  f => f(),
  1000,
  { leading: true }, // debounce one on leading and one on trailing
);

function App(){
   let [state, setState] = useState();

   useEffect(() => debouncer(()=>{
       // you can use state here for new state value
   }),[state])

   return <div />
}
0
hossein alipour

ハンドラーで使用している場合、これがその方法であると私はかなり確信しています。

function useThrottleScroll() {
  const savedHandler = useRef();

  function handleEvent() {}

  useEffect(() => {
    savedHandleEvent.current = handleEvent;
  }, []);

  const throttleOnScroll = useRef(throttle((event) => savedHandleEvent.current(event), 100)).current;

  function handleEventPersistence(event) {
    return throttleOnScroll(event);
  }

  return {
    onScroll: handleEventPersistence,
  };
}
0
user1730335

私の場合、イベントにも合格する必要がありました。これと一緒に行った:

const MyComponent = () => {
  const handleScroll = useMemo(() => {
    const throttled = throttle(e => console.log(e.target.scrollLeft), 300);
    return e => {
      e.persist();
      return throttled(e);
    };
  }, []);
  return <div onScroll={handleScroll}>Content</div>;
};
0
Nelu