Ege*_*Ege 6 lodash reactjs react-hooks
我试图在范围输入移动时更新 React 中子组件的状态。而且,我想使用 Lodash 的 debounce 函数将更新函数触发到父组件的状态,这样我就不会在每次范围输入触发事件时都设置父组件的状态。
但是,在 debounce 延迟之后,所有事件都被触发了。好像我setTimeout在每个范围输入事件上连续调用函数,但不是debounce.
我在这里找不到我缺少的东西。如何在一系列范围输入事件后执行一次传递给“debounce”的函数?
我的简化代码如下所示:
import _ from 'lodash'
import React from 'react'
const Form: React.FC<Props> = props => {
const [selectedStorageSize, setSelectedStorageSize] = React.useState(props.storageSize)
const handleChangeAt = (field, payload) => {
props.handleFormChangeAt(FormField.InstanceDefs, {
...form[FormField.InstanceDefs],
[field]: payload,
})
}
const debouncedChange = _.debounce(
(field, payload) => handleChangeAt(field, payload),
500,
)
return(
<input
required
type="range"
label="Storage Size/GB"
min={50}
max={500}
value={props.selectedStorageSize}
step={5}
onChange={e => {
setSelectedStorageSize(Number(e.target.value))
debouncedChange(FormField.StorageSize, Number(e.target.value))
}}
/>
}
Run Code Online (Sandbox Code Playgroud)
Ret*_*sam 15
_.debounce创建一个函数,该函数可以消除对同一函数实例的连续调用。但这会在每次渲染时创建一个新的,因此连续调用不会调用同一个实例。
您需要在渲染中重用相同的功能。使用钩子有一种直接的方法可以做到这一点……还有一种更好的方法:
useCallback在渲染中保留内容的最直接方法是useMemo/ useCallback。(useCallback实际上只是useMemofor 函数的一个特例)
const debouncedCallback = useCallback(_.debounce(
(field, payload) => handleChangeAt(field, payload),
500,
), [handleChangeAt])
Run Code Online (Sandbox Code Playgroud)
我们遇到了一个问题handleChangeAt:它依赖于 props 并在每次渲染时创建一个不同的实例,以便捕获最新版本的 props。如果我们只创建debouncedCallback一次,我们将捕获 的第一个实例handleChangeAt,并捕获 的初始值props,稍后为我们提供陈旧数据。
我们通过添加[handleChangeAt], 来解决这个问题,以重新创建debouncedCallback每当handleChangeAt更改时。但是,正如所写的那样,每次渲染都会handleChangeAt更改。因此,仅此更改不会改变初始行为:我们仍然会重新创建每个渲染。所以你也需要记住:debouncedCallbackhandleChangeAt
const { handleFormChangeAt } = props;
const handleChangeAt = useCallback((field, payload) => {
handleFormChangeAt(/*...*/)
}, [handleFormChangeAt]);
Run Code Online (Sandbox Code Playgroud)
(如果你不熟悉这种记忆方式,我强烈推荐 Dan Abramov 的完整指南useEffect,即使我们实际上并没有useEffect在这里使用)
这会将问题推到树上,您需要确保任何组件提供props.handleFormChangeAt的也能记住它。但是,否则这个解决方案在很大程度上有效......
useRef前一个解决方案的两个问题:如前所述,它将记忆问题推到了树上(特别是因为您依赖于作为 prop 传递的函数),但重点是这样我们就可以在任何时候重新创建函数需要,以避免过时的数据。
但是重新创建以避免陈旧数据将导致重新创建函数,这将导致去抖动重置:因此,先前解决方案的结果通常会去抖动,但如果道具或状态已更改,则可能不会.
更好的解决方案要求我们真正只创建一次记忆函数,但要以一种避免陈旧数据的方式进行。我们可以通过使用 ref 来做到这一点:
const debouncedFunctionRef = useRef()
debouncedFunctionRef.current = (field, payload) => handleChangeAt(field, payload);
const debouncedChange = useCallback(_.debounce(
(...args) => debouncedFunctionRef.current(...args),
500,
), []);
Run Code Online (Sandbox Code Playgroud)
这将要在 ref 中去抖动的函数的当前实例存储在 ref 中,并在每次渲染时更新它(防止过时数据)。但是,我们没有直接去抖动该函数,而是去抖动一个包装函数,该函数从 ref 读取当前版本并调用它。
由于回调所依赖的唯一对象是 ref(它是一个常量、可变对象),因此可以useCallback将[]其作为其依赖项,因此我们只会按预期对每个组件执行一次去抖动函数。
这种方法可以移动到它自己的自定义钩子中:
const useDebouncedCallback = (callback, delay) => {
const callbackRef = useRef()
callbackRef.current = callback;
return useCallback(_.debounce(
(...args) => callbackRef.current(...args),
delay,
), []);
};
Run Code Online (Sandbox Code Playgroud)
const { useCallback, useState, useRef, useEffect } = React;
const useDebouncedCallback = (callback, delay, opts) => {
const callbackRef = useRef()
callbackRef.current = callback;
return useCallback(_.debounce(
(...args) => callbackRef.current(...args),
delay,
opts
), []);
};
function Reporter({count}) {
const [msg, setMsg] = useState("Click to record the count")
const onClick = useDebouncedCallback(() => {
setMsg(`The count was ${count} when you clicked`);
}, 2000, {leading: true, trailing: false})
return <div>
<div><button onClick={onClick}>Click</button></div>
{msg}
</div>
}
function Parent() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => setCount(x => x+1), 500)
}, [])
return (
<div>
<div>The count is {count}</div>
<Reporter count={count} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<div id="root" />Run Code Online (Sandbox Code Playgroud)
lodash.debounce创建一个新的函数调用,该函数的调用将被反跳。用例场景是创建一次,将结果存储在变量中并多次调用它,以便调用被去抖。每次渲染组件时,您都会创建一个新的去抖函数,因此每次新渲染都会获得新的去抖范围
const debouncedChange = _.debounce(...)您只想对每个组件实例调用一次。我对钩子不太熟悉,但我猜你可以做到这一点
const [debouncedChange] = React.useState(_.debounce(
(field, payload) => handleChangeAt(field, payload),
500,
))
Run Code Online (Sandbox Code Playgroud)
这将在渲染期间创建一次函数,并重用在连续渲染中创建的内容