在中后台管理系统中,我们常常会遇到下面的业务场景:
那就是上面是搜索,下面是带分页的表格。ant design 组件库已经非常贴心的帮我们把表格组件和分页组件封装在了一起。我们只需要修改 column、dataSource 等属性就可以实现不同的页面。写的页面多了,会发现在仍然有一些重复逻辑,那就是请求数据的逻辑:页码修改的时候需要重新请求数据;查询条件修改的时候还要触发请求数据的逻辑。鉴于此,我们将这些逻辑抽取出来,借助 ant design 组件封装一个包含请求数据的表格组。效果:
初始化
npx create-react-app page-table
npm install antd -D
create-react-app 初始化项目,并安装 antd 组件库。
fetch 与 XMLHttpRequest
其实,我们可以用 umi-request、axios 发送请求。本文采用原生的 fetch 发送请求(只要是为了学习😄),推荐阮一峰的《Fetch API 教程》博客( www.ruanyifeng.com/blog/2020/1… )和 MDN 文档( developer.mozilla.org/zh-CN/docs/… )。
这里说下我是使用的几点总结:
-
fetch 会返回一个
Promise对象。即使后端返回的是 404 或者 500,不会报错,只是将返回Promise对象标记为resolved,且会将 resolve 的返回值的 ok 属性设置为 false。仅当网络故障时或请求被阻止时,才会标记为 reject。 -
跨域时,无论 fetch 还是 xhr 都不会自动带上 Cookie。fetch 请求需要设置
credentials: 'include'。XMLHttpRequest请求需要设置:xhr.withCredentials = true; -
取消 fetch 请求,可以借助
AbortController取消请求;而XMLHttpRequest请求是通过xhr.abort()取消请求。 -
AbortController取消 fetch 请求的时候会导致 fetch 返回的 Promise 状态为reject。 具体看如下代码:
const url = "http://localhost:8081/pageTable/v1/famousInfo2?current=1&pageSize=8"
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
console.log(xhr, '-- respone')
}
//请求带上 Cookie
xhr.withCredentials = true;
xhr.open('GET', url, true);
xhr.send();
// 取消请求
setTimeout(() => {
xhr.abort();
}, 1500);
const controller = new AbortController();
const signal = controller.signal;
fetch(url, {
method: 'GET',
mode: 'cors',
credentials: 'include',
signal: signal
}).then(res => {
console.log(res);
});
setTimeout(() => {
controller.abort();
}, 1500)
比较详细介绍 XMLHttpRequest 请求: segmentfault.com/a/119000000…
node 接口
使用 exress 编写了 Restful 风格的查询接口。设置跨域代码 👇:
// 设置跨域访问
app.all("*", function (request, response, next) {
// 设置跨域的域名,* 代表允许任意域名跨域;http://localhost:3000 表示前端请求的 Origin 地址
response.header("Access-Control-Allow-Origin", "http://localhost:3000");
//设置请求头 header 可以加那些属性
response.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
// 设置跨域可以携带 Cookie 信息
response.header('Access-Control-Allow-Credentials', "true");
//设置请求头哪些方法是合法的
response.header(
"Access-Control-Allow-Methods",
"PUT,POST,GET,DELETE,OPTIONS"
);
response.header("Content-Type", "application/json;charset=utf-8");
next();
});
page-table
API
不卖关子,先上封装的表格组件(自带请求数据)的使用 API:
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| url | 必填。请求数据的 URL 地址 | String | |
| columns | 必填。表格列的配置描述 | Array | [ ] |
| pageInfo | 选填。请求的分页参数对象 | Object | {current: 1,pageSize: 8} |
| params | 选填。请求的参数对象 | Object | {} |
| isPagination | 选填。是否需要分页 | Boolean | true |
| type | 选填。第一列显示 check 还是 radio,可选值有 checkbox | radio。不填写什么也不显示。 | String | |
| rowKey | 选填。表格行 key 的取值;默认会给表格数据添加 ROW_ID 字段作为行数据的 key。 | String | "ROW_ID" |
一个比较使用的事件 API:onSelectChange;如果第一列是 checkbox | radio 点击时的回调函数。
const onSelectChange = (selectedRowKeys, selectedRows)=>{
console.log(selectedRowKeys, selectedRows);
}
参数 selectedRowKeys 表示点击选中数据行的 rowKeys 数组;selectedRows 表示点击选中数据行的 rows 数组。
发送数据请求
我们不难想到利用 useEffect 钩子函数在页面加载的时候发送请求,页面卸载的时候取消正在发送的请求。这里非常巧妙的三点:
-
🍓
useEffect(()=>{ fetch() }, [url, params, pageOpitons]);巧妙地将依赖的变量添加到第二个参数中。其中 url、params 是从外部父组件传递进来的参数,也就是父组件的 url 或者查询参数 params 修改的时候,会触发 page-table 组件重新请求后台数据。pageOpitons 是 page-table 组件持有的 state 数据,也就是 page-table 组件可以切换分页触发重新请求后台数据。 -
🍒 对于 page-table 组件的分页功能,父组件也可以通过修改 pageInfo 去触发分页修改。本质上是 React Hook 中父组件
props的修改触发子组件state变化的问题。怎么解决呢?看下面的代码 👇:
// 在 useState 中初始化 pageOpitons 的值
const [pageOpitons, setPageOptions] = useState({
current: pageInfo && pageInfo.current ? pageInfo.current : 1,
pageSize: pageInfo && pageInfo.pageSize ? pageInfo.pageSize : 8
});
useEffect(()=>{
// 当 pageInfo 变化且有值的时候,先判断是否和 pageOpitons 相同,不相同再进行赋值操作。
// 避免重复赋值导致 fetch 数据重复请求。
// isEqual 函数判断 pageInfo, pageOpitons 的 key 和 value 是否一样;一样则表示是相同的。
if(pageInfo && !isEqual(pageInfo, pageOpitons)){
setPageOptions({
current: pageInfo.current,
pageSize: pageInfo.pageSize
})
}
}, [pageInfo]);
// common.js
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
// 判断 obj1 和 obj2 相同:key 和 value 是否完全相同
export function isEqual(obj1, obj2) {
if (!isObject(obj1) && !isObject(obj1)) {
// 值类型比较
return obj1 === obj2;
}
if (obj1 === obj2) {
return true;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
const res = isEqual(obj1[key], obj2[key]);
if (!res) {
return res;
}
}
return true;
}
- 🍍 页面切换的时候取消正在发送的请求。这个时候,上面提到的
AbortController就起到作用了。利用useEffect(()=>{ return ()=>{ abort();}}, []);模拟componentWillUnmount,同时注意 ❗️ 使用useRef来保留AbortController对象,这样才能保证取消的时候是同一个。
const abortControllerRef = useRef(new AbortController());
//取消请求
useEffect(() => {
return () => abortControllerRef.current.abort()
}, []);
源码地址
git: github.com/YY88Xu/page… 欢迎大家 fork,提宝贵建议 ❤️。