使用 useEffect 的清理取消请求非常容易
useEffect(() => {
let ignore = false;
fetchData(id).then(data => {
if (!ignore) {
setData(data);
}
});
return () => (ignore = true);
}, [id]);
我想做类似的事情,但我需要使用 useInterval 轮询数据
我想用fetchData(id)
轮询数据,如果请求已触发,但在响应解决之前id
更改,则忽略返回的响应。
针对您的特定情况进行黑客
攻击假设您收到id
作为道具或类似的东西,这会起作用吗?
const Foo = ({ id }) => {
const [data, setData] = useState()
const passedId = useRef()
passedId.current = id
useInterval(() => {
fetchData(id)
.then(response => id === passedId.current && setData(response))
}, 5000)
// Render some JSX with the data
我在本地测试了一些与此非常相似的东西,基本上发生的事情是这样的:
Foo
收到id = 6
6
存储在passedId
useInterval
刻度,我们要求id = 6
- 组件接收
id = 7
7
存储在passedId
id = 6
请求已完成,但passedId.current === 7
因此不调用setData
id = 7
请求完成、id === passedId.current
和setData
称为
把这样的事情弄useInterval
注意 - 未测试
取消效果如此容易的原因可能是因为该函数返回自己的清理,因此ignore
不必在外部限定范围。但setInterval
不允许返回值。我们也许可以使用setTimeout
来解决这个问题:
function useInterval(callback, delay) {
useEffect(() => {
let cleanup
let id
function tick() {
cleanup = callback()
id = setTimeout(tick, delay)
}
if (delay !== null) {
tick()
return () => {
clearTimeout(id)
cleanup && cleanup()
}
}
return () => cleanup && cleanup()
}, [callback, delay])
这样做的一个问题是,现在我们在依赖项数组中callback
,因此应使用useCallback
创建提供给useInterval
的回调:
const Foo = ({ id }) => {
const [data, setData] = useState()
const pollData = useCallback(() => {
let shouldUpdate = true
fetchData(id).then(resp => shouldUpdate && setData(resp))
return () => shouldUpdate = false
}, [id])
useInterval(pollData, 5000)
// Render some JSX with the data
- 调用
useInterval
时,我们设置一个执行callback
的useEffect
- 当
callback
执行时,返回值(它的清理(存储在cleanup
中,该的范围限定为效果,并且setTimeout
设置为在delay
毫秒内重新执行 - 如果
callback
发生变化(即我们得到一个新的id
(,那么我们清除超时并运行上一个回调的清理 delay
毫秒后,再次执行tick
这是我想出的useInterval
function (callback, options, cleanup) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (options.delay !== null) {
let id = setInterval(tick, options.delay);
return () => {
clearInterval(id);
cleanup && cleanup();
}
}
return () => cleanup && cleanup();
}, [options]);
}
我这么用它
const [fetchOptions, setFetchOptions] = useState({delay: 5000, id: 'someid'});
let ignore = false;
useInterval(
() => {
fetchData(fetchOptions.id).then(data => {
if (!ignore) {
setData(data);
}
});
},
fetchOptions,
() => (ignore = true),
);
我不确定是否有更好的方法来写这个。我担心的是ignore
变量的作用域为函数/组件的上下文。在问题的代码示例中,ignore 变量位于 useEffect 中,感觉更干净。
这种方法的缺点是 fetchOptions 需要是一个 useState 变量,否则它会在每个渲染上重置 useInterval 钩子,如果它只是函数中的一个常量。