使用 useInterval 钩子取消异步请求



使用 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

我在本地测试了一些与此非常相似的东西,基本上发生的事情是这样的:

  1. Foo收到id = 6
  2. 6存储在passedId
  3. useInterval刻度,我们要求id = 6
  4. 组件接收id = 7
  5. 7存储在passedId
  6. id = 6请求已完成,但passedId.current === 7因此不调用setData
  7. id = 7请求完成、id === passedId.currentsetData称为

把这样的事情弄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
  1. 调用useInterval时,我们设置一个执行callbackuseEffect
  2. callback执行时,返回值(它的清理(存储在cleanup中,该的范围限定为效果,并且setTimeout设置为在delay毫秒内重新执行
  3. 如果callback发生变化(即我们得到一个新的id(,那么我们清除超时并运行上一个回调的清理
  4. 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 钩子,如果它只是函数中的一个常量。

相关内容

  • 没有找到相关文章

最新更新