[英]How can I grab a fresh state inside a useEffect hook body?
我正在尝试构建一个去抖动钩子。 我以前见过几个实现,但没有一个适合我的需要:通常它们会延迟执行处理程序,直到调用处理程序的尝试停止足够长的时间。
useEffect(() => {
timeout = setTimeout(handler, 500);
return () => {
if (timeout){
clearTimeout(timeout);
}
}
}, [handler]);
(或类似的东西。)我认为这是有缺陷的,因为如果意图是避免向长时间运行的 function 发送垃圾邮件,它不会考虑 function 是否在超时内返回。 如果在这种情况下获取搜索结果的时间超过 500 毫秒怎么办?
相反,我想尝试运行一个长时间运行的 function(处理程序)。如果没有运行,则执行处理程序并返回其 promise。此外,使用 finally 块检查输入是否已更改,并且如果是这样,再次解雇处理程序。
我想要的用法:
const [input, setInput] = useState<string>("");
const debouncedPromise = useDebounce(() => asyncFunction(input), [input]);
只要输入发生变化,如果处理程序尚未运行,它就可以排队。
这是我写的代码:
import { DependencyList, useEffect, useRef, useState } from "react";
interface IState<T> {
handler?: () => Promise<T>;
promise?: Promise<T>;
isWaiting: boolean;
}
export const useDebounce = <T>(handler: () => Promise<T>, deps: DependencyList = []): Promise<T> | undefined => {
const [state, setState] = useState<IState<T>>({
handler,
isWaiting: false
});
const stopWaiting = () => {
console.log("stopWaiting");
setState(previousState => ({ ...previousState, waiting: false }));
};
const run = () => {
const promise = handler();
promise.finally(stopWaiting);
setState({
handler,
isWaiting: true,
promise,
});
};
useEffect(() => {
console.log("\nuseEffect");
console.log(`deps: ${deps}`)
console.log(`state.isWaiting: ${state.isWaiting}`);
console.log(`state.handler: ${state.handler}`);
console.log(`state.promise: ${state.promise}`);
if (state.isWaiting){
console.log(">>> state.isWaiting")
return;
}
if (handler === state.handler){
console.log(">>> handler === state.handler")
return;
}
if (state.isWaiting && state.promise && state.handler !== handler){
console.log(">>> state.isWaiting && state.promise && state.handler !== handler")
state.promise.finally(run);
return;
}
if (handler !== state.handler){
console.log(">>> handler !== state.handler")
run();
}
console.log("end useEffect");
}, [...deps, state.isWaiting]);
return state.promise;
};
它适用于第一次调用,但它似乎永远不会释放 state.isWaiting 以允许触发子序列、挂起的处理程序:
useEffect UseDebounce.ts:32
deps: T UseDebounce.ts:33
state.isWaiting: false UseDebounce.ts:34
state.handler: function () {
return asyncFunction(input);
} UseDebounce.ts:35
state.promise: undefined UseDebounce.ts:36
>>> handler !== state.handler UseDebounce.ts:55
asyncFunction called with T UseDebounce.tsx:9
end useEffect UseDebounce.ts:59
useEffect UseDebounce.ts:32
deps: T UseDebounce.ts:33
state.isWaiting: true UseDebounce.ts:34
state.handler: function () {
return asyncFunction(input);
} UseDebounce.ts:35
state.promise: [object Promise] UseDebounce.ts:36
>>> state.isWaiting UseDebounce.ts:39
asyncFunction resolved with T UseDebounce.tsx:12
stopWaiting UseDebounce.ts:16
useEffect UseDebounce.ts:32
deps: Ti UseDebounce.ts:33
state.isWaiting: true UseDebounce.ts:34 // This should be false at this point
我想我被陈旧的 state 困住了。我该如何解决这个问题? 有没有更好的钩子可以用来获得我想要的结果? 一旦输入“稳定下来”,我如何停止触发处理程序?
它似乎永远不会释放
state.isWaiting
。 我想我被陈旧的 state 困住了。
我认为你的问题太新鲜了 state。特别是,你每次都用一个新的、不同的handler
调用钩子,所以handler === state.handler
可能是真的是初始化 state 的初始值同一个处理程序。 您可能需要在传递的 function 上使用useCallback
。
但即便如此,你的代码也有问题。 如果在 function 仍在运行时输入多次更改,您将调用state.promise.finally(run);
多次,哪些调度在同一个 promise 上run
(实际上是一些陈旧的handler
)多次,导致它们同时执行。
有没有更好的钩子可以用来获得我想要的结果?
我不会(仅)使用 state,而是为等待在下一次 function 调用中使用的输入使用一个简单的共享可变引用。 还让run
function 自己继续最新的输入,而不是安排一个难以取消的固定处理程序。
所以我会写
interface ResultState<T> {
running: boolean;
result?: PromiseSettledResult<T>;
}
function useAsyncEvaluation<T, Args extends DependencyList>(fn: (...args: Args) => Promise<T>, input: Args): ResultState<T> {
const args = useRef<Args | undefined>();
const [state, setState] = useState<ResultState<T>>({running: false});
function run() {
const next = args.current;
if (!next) return false;
args.current = undefined;
fn(...next).then(
value => ({status: "fulfilled", value}),
reason => ({status: "rejected", reason})
).then(result => {
setState({running: run(), result});
);
return true;
}
useEffect(() => {
args.current = input;
setState(old => {
if (old.running) {
return old;
} else {
// calling run() inside the state update prevents race conditions
return {running: run(), result: old.result};
}
});
return () => {
args.current = undefined;
};
}, input)
return state;
}
请注意,这会在第一次渲染期间更新效果中的 state并响应input
更改,这有点不合需要。 无论如何,您可能实际上并不想这样做——而是从挂钩中返回 function 并从您的事件处理程序中调用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.