私は、機能コンポーネントで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
内のメソッドはレンダリングごとに再宣言されるため、スロットル効果は機能しません。
誰かが簡単な解決策を持っていますか?
しばらくしてから、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
にプッシュするときは注意してください。
次のような小さなカスタムフックである可能性があります。
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つの単純なフック( se-throttled-effect と se-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}
);
}
私はこのようなものを使用し、それはうまくいきます:
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 />
}
ハンドラーで使用している場合、これがその方法であると私はかなり確信しています。
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,
};
}
私の場合、イベントにも合格する必要がありました。これと一緒に行った:
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>;
};