Ale*_*nic 24 throttling lodash reactjs react-hooks
我正在尝试throttle在功能组件中使用lodash中的方法,例如:
const App = () => {
const [value, setValue] = useState(0)
useEffect(throttle(() => console.log(value), 1000), [value])
return (
<button onClick={() => setValue(value + 1)}>{value}</button>
)
}
Run Code Online (Sandbox Code Playgroud)
由于内部方法useEffect在每次渲染时都会重新声明,因此限制效果不起作用。
有没有人有一个简单的解决方案?
Tod*_*ton 47
我创建了自己的自定义钩子useDebouncedEffect,该钩子将等待执行useEffect直到状态在延迟期间没有更新。
在此示例中,您停止单击按钮 1 秒钟后,您的效果将记录到控制台。
沙盒示例 https://codesandbox.io/s/react-use-debounced-effect-6jppw
App.jsximport { useState } from "react";
import { useDebouncedEffect } from "./useDebouncedEffect";
const App = () => {
const [value, setValue] = useState(0)
useDebouncedEffect(() => console.log(value), [value], 1000);
return (
<button onClick={() => setValue(value + 1)}>{value}</button>
)
}
export default App;
Run Code Online (Sandbox Code Playgroud)
useDebouncedEffect.jsimport { useState } from "react";
import { useDebouncedEffect } from "./useDebouncedEffect";
const App = () => {
const [value, setValue] = useState(0)
useDebouncedEffect(() => console.log(value), [value], 1000);
return (
<button onClick={() => setValue(value + 1)}>{value}</button>
)
}
export default App;
Run Code Online (Sandbox Code Playgroud)
除非您想看到警告,否则禁用详尽的 deps 的注释是必需的,因为 lint 总是会抱怨作为依赖项没有效果。添加 effect 作为依赖项将在每次渲染时触发 useEffect。相反,您可以添加检查以useDebouncedEffect确保它已通过所有依赖项。(见下文)
将详尽的依赖项检查添加到 useDebouncedEffect
如果你想让 eslint 检查useDebouncedEffect详尽的依赖项,你可以将它添加到 eslint 配置中package.json
"eslintConfig": {
"extends": [
"react-app"
],
"rules": {
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useDebouncedEffect"
}]
}
},
Run Code Online (Sandbox Code Playgroud)
sky*_*yer 34
您可能(可能需要)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>
)
}
Run Code Online (Sandbox Code Playgroud)
至于useCallback:
它可能也可以工作
const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);
Run Code Online (Sandbox Code Playgroud)
但是,如果value更改后尝试重新创建回调,则:
const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);
Run Code Online (Sandbox Code Playgroud)
我们可能会发现它不会延迟执行:value更改后,回调将立即重新创建并执行。
因此,我发现useCallback在延迟运行的情况下不会提供明显的优势。由你决定。
[UPD]最初是
const throttled = useRef(throttle(() => console.log(value), 1000))
useEffect(throttled.current, [value])
Run Code Online (Sandbox Code Playgroud)
但是这种方式throttled.current已value通过闭包绑定到初始(0)。因此,即使在下一个渲染中也从未改变过。
因此,useRef由于闭包功能,在将函数推入时要小心。
for*_*d04 20
useThrottle , useDebounceconst App = () => {
const [value, setValue] = useState(0);
// called at most once per second (same API with useDebounce)
const throttledCb = useThrottle(() => console.log(value), 1000);
// usage with useEffect: invoke throttledCb on value change
useEffect(throttledCb, [value]);
// usage as event handler
<button onClick={throttledCb}>log value</button>
// ... other render code
};
Run Code Online (Sandbox Code Playgroud)
useThrottle(洛达什)import _ from "lodash"
function useThrottle(cb, delay) {
const options = { leading: true, trailing: false }; // add custom lodash options
const cbRef = useRef(cb);
// use mutable ref to make useCallback/throttle not depend on `cb` dep
useEffect(() => { cbRef.current = cb; });
return useCallback(
_.throttle((...args) => cbRef.current(...args), delay, options),
[delay]
);
}
Run Code Online (Sandbox Code Playgroud)
const App = () => {
const [value, setValue] = useState(0);
// called at most once per second (same API with useDebounce)
const throttledCb = useThrottle(() => console.log(value), 1000);
// usage with useEffect: invoke throttledCb on value change
useEffect(throttledCb, [value]);
// usage as event handler
<button onClick={throttledCb}>log value</button>
// ... other render code
};
Run Code Online (Sandbox Code Playgroud)
import _ from "lodash"
function useThrottle(cb, delay) {
const options = { leading: true, trailing: false }; // add custom lodash options
const cbRef = useRef(cb);
// use mutable ref to make useCallback/throttle not depend on `cb` dep
useEffect(() => { cbRef.current = cb; });
return useCallback(
_.throttle((...args) => cbRef.current(...args), delay, options),
[delay]
);
}
Run Code Online (Sandbox Code Playgroud)
useDebounce(洛达什)import _ from "lodash"
function useDebounce(cb, delay) {
// ...
const inputsRef = useRef({cb, delay}); // mutable ref like with useThrottle
useEffect(() => { inputsRef.current = { cb, delay }; }); //also track cur. delay
return useCallback(
_.debounce((...args) => {
// Debounce is an async callback. Cancel it, if in the meanwhile
// (1) component has been unmounted (see isMounted in snippet)
// (2) delay has changed
if (inputsRef.current.delay === delay && isMounted())
inputsRef.current.cb(...args);
}, delay, options
),
[delay, _.debounce]
);
}
Run Code Online (Sandbox Code Playgroud)
const App = () => {
const [value, setValue] = useState(0);
const invokeDebounced = useThrottle(
() => console.log("changed throttled value:", value),
1000
);
useEffect(invokeDebounced, [value]);
return (
<div>
<button onClick={() => setValue(value + 1)}>{value}</button>
<p>value will be logged at most once per second.</p>
</div>
);
};
function useThrottle(cb, delay) {
const options = { leading: true, trailing: false }; // pass custom lodash options
const cbRef = useRef(cb);
useEffect(() => {
cbRef.current = cb;
});
return useCallback(
_.throttle((...args) => cbRef.current(...args), delay, options),
[delay]
);
}
ReactDOM.render(<App />, document.getElementById("root"));Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>Run Code Online (Sandbox Code Playgroud)
1. 你可以用你自己的throttle或debounce代码替换 Lodash ,比如:
import _ from "lodash"
function useDebounce(cb, delay) {
// ...
const inputsRef = useRef({cb, delay}); // mutable ref like with useThrottle
useEffect(() => { inputsRef.current = { cb, delay }; }); //also track cur. delay
return useCallback(
_.debounce((...args) => {
// Debounce is an async callback. Cancel it, if in the meanwhile
// (1) component has been unmounted (see isMounted in snippet)
// (2) delay has changed
if (inputsRef.current.delay === delay && isMounted())
inputsRef.current.cb(...args);
}, delay, options
),
[delay, _.debounce]
);
}
Run Code Online (Sandbox Code Playgroud)
const App = () => {
const [value, setValue] = useState(0);
const invokeDebounced = useDebounce(
() => console.log("debounced", value),
1000
);
useEffect(invokeDebounced, [value]);
return (
<div>
<button onClick={() => setValue(value + 1)}>{value}</button>
<p> Logging is delayed until after 1 sec. has elapsed since the last invocation.</p>
</div>
);
};
function useDebounce(cb, delay) {
const options = {
leading: false,
trailing: true
};
const inputsRef = useRef(cb);
const isMounted = useIsMounted();
useEffect(() => {
inputsRef.current = { cb, delay };
});
return useCallback(
_.debounce(
(...args) => {
// Don't execute callback, if (1) component in the meanwhile
// has been unmounted or (2) delay has changed
if (inputsRef.current.delay === delay && isMounted())
inputsRef.current.cb(...args);
},
delay,
options
),
[delay, _.debounce]
);
}
function useIsMounted() {
const isMountedRef = useRef(true);
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
return () => isMountedRef.current;
}
ReactDOM.render(<App />, document.getElementById("root"));Run Code Online (Sandbox Code Playgroud)
2.useThrottle可以缩短,如果总是与useEffect(相同的useDebounce)一起使用:
const App = () => {
// useEffect now is contained inside useThrottle
useThrottle(() => console.log(value), 1000, [value]);
// ...
};
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>Run Code Online (Sandbox Code Playgroud)
const debounceImpl = (cb, delay) => {
let isDebounced = null;
return (...args) => {
clearTimeout(isDebounced);
isDebounced = setTimeout(() => cb(...args), delay);
};
};
const throttleImpl = (cb, delay) => {
let isThrottled = false;
return (...args) => {
if (isThrottled) return;
isThrottled = true;
cb(...args);
setTimeout(() => {
isThrottled = false;
}, delay);
};
};
const App = () => {
const [value, setValue] = useState(0);
const invokeThrottled = useThrottle(
() => console.log("throttled", value),
1000
);
const invokeDebounced = useDebounce(
() => console.log("debounced", value),
1000
);
useEffect(invokeThrottled, [value]);
useEffect(invokeDebounced, [value]);
return <button onClick={() => setValue(value + 1)}>{value}</button>;
};
function useThrottle(cb, delay) {
const cbRef = useRef(cb);
useEffect(() => {
cbRef.current = cb;
});
return useCallback(
throttleImpl((...args) => cbRef.current(...args), delay),
[delay]
);
}
function useDebounce(cb, delay) {
const cbRef = useRef(cb);
useEffect(() => {
cbRef.current = cb;
});
return useCallback(
debounceImpl((...args) => cbRef.current(...args), delay),
[delay]
);
}
ReactDOM.render(<App />, document.getElementById("root"));Run Code Online (Sandbox Code Playgroud)
Meh*_*ani 16
它可能是一个很小的自定义钩子,如下所示:
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, timeout]);
return state;
}
Run Code Online (Sandbox Code Playgroud)
用法示例:
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 (
// ...
);
}
// ...
Run Code Online (Sandbox Code Playgroud)
注意:您可能知道,useEffect始终在初始渲染上运行,因此如果您使用我的答案,您可能会看到组件的渲染运行两次,别担心,您只需要编写另一个自定义钩子。查看我的其他答案以获取更多信息。
我想使用以下方法加入我的节流和去抖输入的聚会useState:
// import { useState, useRef } from 'react' // nomral import
const { useState, useRef } = React // inline import
// Throttle
const ThrottledInput = ({ onChange, delay = 500 }) => {
const t = useRef()
const handleChange = ({ target }) => {
if (!t.current) {
t.current = setTimeout(() => {
onChange(target.value)
clearTimeout(t.current)
t.current = null
}, delay)
}
}
return (
<input
placeholder="throttle"
onChange={handleChange}
/>
)
}
// Debounce
const DebouncedInput = ({ onChange, delay = 500 }) => {
const t = useRef()
const handleChange = ({ target }) => {
clearTimeout(t.current)
t.current = setTimeout(() => onChange(target.value), delay)
}
return (
<input
placeholder="debounce"
onChange={handleChange}
/>
)
}
// ----
ReactDOM.render(<div>
<ThrottledInput onChange={console.log} />
<DebouncedInput onChange={console.log} />
</div>, document.getElementById('root'))Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>Run Code Online (Sandbox Code Playgroud)
我为此用例编写了两个简单的钩子(use-throttled-effect和use-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}
);
}
Run Code Online (Sandbox Code Playgroud)
还有一个实施。自定义挂钩:
function useThrottle (func, delay) {
const [timeout, saveTimeout] = useState(null);
const throttledFunc = function () {
if (timeout) {
clearTimeout(timeout);
}
const newTimeout = setTimeout(() => {
func(...arguments);
if (newTimeout === timeout) {
saveTimeout(null);
}
}, delay);
saveTimeout(newTimeout);
}
return throttledFunc;
}
Run Code Online (Sandbox Code Playgroud)
和用法:
const throttledFunc = useThrottle(someFunc, 200);
Run Code Online (Sandbox Code Playgroud)
希望这会对某人有所帮助。
在 useCallback 钩子的帮助下去抖动。
import React, { useState, useCallback } from 'react';
import debounce from 'lodash.debounce';
function App() {
const [value, setValue] = useState('');
const [dbValue, saveToDb] = useState(''); // would be an API call normally
// highlight-starts
const debouncedSave = useCallback(
debounce(nextValue => saveToDb(nextValue), 1000),
[], // will be created only once initially
);
// highlight-ends
const handleChange = event => {
const { value: nextValue } = event.target;
setValue(nextValue);
// Even though handleChange is created on each render and executed
// it references the same debouncedSave that was created initially
debouncedSave(nextValue);
};
return <div></div>;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
11548 次 |
| 最近记录: |