取消请求

React 中当前正在发出请求的组件从页面上卸载了,理想情况下这个请求也应该取消掉,那么如何把请求的取消和页面的卸载关联在一起呢?

这里要考虑利用 useEffect 传入函数的返回值:

useEffect(() => {
return () => {
// 页面卸载时执行
};
}, []);
复制代码

假设我们的请求是利用 fetch,那么还有一个需要运用的知识点:AbortController,简单看一下它的用法:

const abortController = new AbortController();

fetch(url, {
// 这里传入 signal 进行关联
signal: abortController.signal,
}); // 这里调用 abort 即可取消请求
abortController.abort();
复制代码

那么结合 React 封装一个 useFetch 的 hook:

export function useFetch = (config, deps) => {
const abortController = new AbortController()
const [loading, setLoading] = useState(false)
const [result, setResult] = useState() useEffect(() => {
setLoading(true)
fetch({
...config,
signal: abortController.signal
})
.then((res) => setResult(res))
.finally(() => setLoading(false))
}, deps) useEffect(() => {
return () => abortController.abort()
}, []) return { result, loading }
}
复制代码

那么比如在路由发生切换,Tab 发生切换等场景下,被卸载掉的组件发出的请求也会被中断。

深比较依赖

在使用 useEffect 等需要传入依赖的 hook 时,最理想的状况是所有依赖都在真正发生变化的时候才去改变自身的引用地址,但是有些依赖不太听话,每次渲染都会重新生成一个引用,但是内部的值却没变,这可能会让 useEffect 对于依赖的「浅比较」没法正常工作。

比如说:

const getDep = () => {
return {
foo: 'bar',
};
}; useEffect(() => {
// 无限循环了
}, [getDep()]);
复制代码

这是一个人为的例子,由于 getDeps 函数返回的对象每次执行都是一个全新的引用,所以会导致触发渲染->effect->渲染->effect 的无限更新。

有一个比较取巧的解决办法,把依赖转为字符串:

const getDep = () => {
return {
foo: 'bar',
};
}; const dep = JSON.stringify(getDeps()); useEffect(() => {
// ok!
}, [dep]);
复制代码

这样对比的就是字符串 "{ foo: 'bar' }" 的值,而不是对象的引用,那么只有在值真正发生变化时才会触发更新。

当然最好还是用社区提供的方案:useDeepCompareEffect,它选用深比较策略,对于对象依赖来说,它逐个对比 key 和 value,在性能上会有所牺牲。

如果你的某个依赖触发了多次无意义的接口请求,那么宁愿选用 useDeepCompareEffect ,在对象比较上多花费些时间可比重复请求接口要好得多。

useDeepCompareEffect 大致原理:

import { isEqual } from 'lodash';
export function useDeepCompareEffect(fn, deps) {
const trigger = useRef(0);
const prevDeps = useRef(deps);
if (!isEqual(prevDeps.current, deps)) {
trigger.current++;
}
prevDeps.current = deps;
return useEffect(fn, [trigger.current]);
}
复制代码

真正传入 useEffect 用以更新的是 trigger 这个数字值。用useRef 保留上一次传入的依赖,每次都利用 lodash 的 isEqual 对本次依赖和旧依赖进行深比较,如果发生变化,则让 trigger 的值增加。

当然我们也可以用 fast-deep-equal 这个库,根据官方的 benchmark 对比,它比 lodash 的效率高 7 倍左右。

以 URL 为数据仓库

在公司内部的后台管理项目中,无论你做的系统面向的人群是运营还是开发,都会涉及到分享,那么保留「页面状态」就非常重要了。比如我是运营 A,在使用一个内部数据平台,我一定是想向运营 B 分享某 App 的消费数据的第二页,并且筛选为某个用户的状态的网页,并且进行讨论。那么状态和 URL 同步就尤为重要了。

在传统的状态管理思路中,我们需要在代码里用reduxrecoil等库去做一系列的数据管理,但是如果把 URL 后面的那串 query 想象成数据仓库呢?是不是也可以,尝试配合react-router封装一下。

export function useQuery() {
const history = useHistory();
const { search, pathname } = useLocation();
// 保存query状态
const queryState = useRef(qs.parse(search));
// 设置query
const setQuery = handler => {
const nextQuery = handler(queryState.current);
queryState.current = nextQuery;
// replace会使组件重新渲染
history.replace({
pathname: pathname,
search: qs.stringify(nextQuery),
});
};
return [queryState.current, setQuery];
}
复制代码

在组件中,可以这样使用:

const [query, setQuery] = useQuery();

// 接口请求依赖 page 和 size
useEffect(() => {
api.getUsers();
}, [query.page, query, size]); // 分页改变 触发接口重新请求
const onPageChange = page => {
setQuery(prevQuery => ({
...prevQuery,
page,
}));
};
复制代码

这样,所有的页面状态更改都会自动同步到 URL,非常方便。

最新文章

  1. .net两个对象比较,抛出不一样字段的结果
  2. 【C++】DDX_Control、SubclassWindow和SubclassDlgItem的区别
  3. js DOM Node类型
  4. 当他们也换成了Linux OS
  5. 第二课:判断js变量的类型以及domReady的原理
  6. 转:有关Java泛型的类型擦除(type erasing)
  7. FTP文件操作之创建目录
  8. React入门---可复用组件-10
  9. php+sqlserver实现分页效果
  10. C# 中函数内定义函数的委托方法
  11. Ant Design Pro 学习二 新建菜单-布局
  12. Error:unsupported class file version 52.0问题的解决
  13. BZOJ_2435_[Noi2011]道路修建_dfs
  14. GCC 警告
  15. SQL Server 数据库部分常用语句小结(二)
  16. cefsharp插入自定义JS
  17. lombok标签之@Data @AllArgsConstructor @@NoArgsConstructor -如何去除get,set方法。@Data注解和如何使用,lombok
  18. python zip文件压缩和解压
  19. ubuntu 关闭 笔记本键盘背景灯
  20. hdu1024线性dp

热门文章

  1. python了解未知函数的方法
  2. 信号量解决理发师问题(barber)
  3. 鸟哥的linux私房菜——第五章学习(Linux的文件权限与目录配置)
  4. Netty(五)Netty 高性能之道
  5. HDU 4746 Mophues(莫比乌斯反演)题解
  6. React render algorithm & Fiber vs Stack
  7. ts 在Function上创建静态属性和方法
  8. React & Desktop App
  9. URLSearchParams & GET Query String & JSON
  10. vue的filter用法,检索内容