工作中写React 学到了什么
# 一、前言
工作过程中的技术栈主要是 React + React Router / React + Umijs ,会有 class 和 hooks 混用的情况。(hooks化不彻底)
这篇文章主要总结在开发中遇到的一些问题和解决方法
代码都是简化后的代码,不代表生产环境,但是其思想是相通的。
# 二、按需加载
一个中大型项目打包后会生成5M~20M的单个js文件
网络环境缓慢时,加载这种资源会显得非常卡,体验不好
React 提供了一个按需加载API,把部分路由(目录)抽离成多个小文件,再通过配置 webpack 打包,即可实现按需加载功能
- React 版本 > 16.6
使用 react.lazy
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import React, { Suspense, lazy } from "react";
const Program1 = lazy(() => import("./Program1"));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route path="/program1" component={Program1} />
</Switch>
</Suspense>
</Router>
);
配置 webpack 4+版本
...
output: {
path: path.resolve(root, 'dist/dev'),
publicPath: '/',
filename: 'index.[hash:10].js',
chunkFilename: '[name].[chunkhash:10].chunk.js',
},
...
webpack and react-router按需加载 (opens new window)
# 三、终止请求和 时序控制CAS
# 3.1、终止请求
XHR、Fetch、Axios终止请求方式 (opens new window)
结合 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 }
}
# 3.2、时序控制 CAS
页面中请求同一接口三次,顺序分别是 A B C
响应顺序是 A C B ,B 把 C 的结果给覆盖了,显然时有问题的
CAS(CompareAndSet),先比较在赋值,更偏向于后端的概念,但是前端也能用得上。
方法就是前端请求接口时,维护一个字段比如 { cas: 1633681618183 }
后端原样返回该字段,前端响应接口时比较一下,再存入 Store
并发扣款,如何保证数据的一致性? (opens new window)
并发扣款一致性优化,CAS下ABA问题 (opens new window)
# 四、以 URL 为数据仓库
在开发项目中,经常会涉及浏览器回退、分享等操作。那么保留「页面状态」就非常重要了。
如用户筛选某些状态,而后退回到该页面时,状态值需要保留的化,状态和 URL 同步尤为重要。
在传统状态管理思路中,常用 redux、recoil 等库去做一系列数据管理,如果把 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,
}));
};