前言
从Vue
到 React Class
再到 React Hooks
,使用过程中其实遇到过很多坑,在面对问题的时候,感觉每一个都非要的“要命”,然而解决完之后又感觉不是很大的问题,但是这些小问题如果进行深究的话,是可以学习到很多关于框架的原理知识的。
在接下来的工作以及学习中,我会一直持续更新各种“坑”,希望大家可以共同学习以及进步,奥力给~
坑One
一、场景描述
使用React Hooks
、Antd Table
组件实现列表数据展示
- 通过用户角色控制“操作”列是否展示(超级管理员无操作权限,只能查看)
- 点击“去支付”跳转支付页面,若无正常进行支付流程,在返回列表要刷新列表,并展示倒计时
二、问题描述
- 点击“查看详情”按钮,跳转详情页,传递的角色为初始化角色,而不是根据接口查询返回的最新数据;
- 点击“去支付”跳转到支付页面,返回到上一级页面时,接口重新调用且状态已修改为“待付款”,但是并没有在页面展示最新状态及倒计时;
- 倒计时实现过程出现页面数据不重新渲染的问题;
三、实现细节(存在问题)
// 用户角色:0-超级管理员、1-代理商
const [role, setRole] = useState<number>(0);
// 从接口请求的数据源
const [list, setList] = useState<any []>([]);
// list数据结构如下:
{
key: '1',
id: 'NO202203062235',
status: 1,
money: '0.01',
}
// 定义表格
const [columns, setColumns] = useState<any []>([
{
title: '合同ID',
dataIndex: 'id',
key: 'id',
},
{
title: '支付状态',
dataIndex: 'status',
key: 'status',
render(text: any) {
const statusObj: any = {
1: '未支付',
2: '待付款',
3: '已支付'
}
return statusObj[text] || '-'
}
},
{
title: '金额',
dataIndex: 'money',
key: 'money',
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
render(record: any) {
return (
<>
{
record.status === 1 &&
<Button onClick={() => payHandler(record)}>去支付</Button>
}
{
record.status === 2 &&
<Button onClick={() => cancelPayHandler(record)}>取消支付</Button>
}
{
record.status === 2 && record.count > 0 &&
<CountDown count={record.count} />
}
<Button onClick={() => detailHandler(record)}>查看详情</Button>
</>
)
}
}
]);
// Table渲染
<Table
dataSource={list}
columns={columns}
/>
// JS逻辑
// 1、获取用户角色
const getRole = async () => {
const res = await api.role();
res.code === 0 ? setRole(res.role) : '';
// 根据角色显示“操作”列
res.role === 0 ? setColumns(columns.filter((item) => item.dataIndex !== 'operation')) : ''
}
// 2、根据角色获取列表数据
const getRole = async () => {
const res = await api.list();
res.code === 0 ? setList(res.data) : ''
}
// 3、去支付
const payHandler = () => {
// 跳转支付页面,url为处理过的页面URL
window.location.href = url;
}
// 4、查看详情
const detailHandler = () => {
history.push(`/detail?role=$role}`)
}
useEffect(() => {
getRole();
getRole();
}, [])
四、问题原因
1、为什么点击“查看详情”按钮,获取到的role
为最初设置的默认值0
,而不是根据接口获取的最新值?
原因:
useState
初始化值,只有在第一次有效
例如:
// 父组件 App.js
const [name, setName] = useState('张三')
const userInfo = { name }
return <div>
<button onClick={() => setName('李四')}>setName</button>
<Child userInfo={userInfo} />
</div>
// 子组件 Child.js
// 【注意】
// render(首次渲染): 初始化 state
// re-render(更新渲染): 只恢复初始化的值(即张三),不会在重新设置新的值,只能使用setName去手动修改
const [name, setName] = useState(userInfo.name)
return <div>
// 点击App中的 setName 按钮,此处会对应修改
<p>Child, props name: {userInfo.name}<p>
// 点击App中的 setName 按钮,此处不会对应修改
<p>Child, state name: {name}<p>
</div>
实际应用中问题原因:
使用useState
[columns, setColumns]
来定义表格源,所以表格的操作的“查看详情”中render
中已经默认记住role
的初始值0
,当调用接口异步获取新的角色值之后,由于只调用了setRole
修改role
的值,并没有手动调用setColumns
去修改columns
,所以columns
中的数据依旧是
默认的初始值,因此role
一直是默认值0
解决方法:
columns
不使用useState
初始化,只定义为一个常量值,这样操作一列无法去除,可以使用‘-’
展示操作列;columns
不使用useState
初始化,只定义为一个常量值,操作列可以根据条件判断进而控制列宽即width
为0
;- 依旧使用
useState
设置columns
,当获取到角色时,除了设置role
外,还要手动设置columns
; 4.既然无法获取最新的role
,那么在获取list
列表的时候,可以在列表的每一项中增加一个role
字段,这样在record
中都存在一个最新的role
值,但是这样的缺点是会创建多余的变量以及消耗内存空间
2、返回列表页面,接口重新调用,数据已更新为“待付款”,页面也已经刷新,为什么不显示倒计时?
原因:
当调用浏览器的“返回”操作后,如果页面URL
参数接口参数都没有变化的时候,浏览器默认认为内容没有任何变化,此时即使浏览器重新刷新,接口重新调用,浏览器也会直接拿缓存中的数据进行渲染,因为这样速度很快,所以出现了不展示倒计时的情况。
解决方法:
在接口请求后面增加随机参数_v=Math.randow()
或者_v=Date.now()
详细解释及说明可以参考:# 浏览器缓存之返回上一级页面,数据不刷新
3、倒计时为什么console
的数据在实时改变,然后页面数据却没有变化?
原因:
useEffect
内部不能修改state
详细原因及解决方法可以参考:# React Hooks实现倒计时及避坑
更新 Loading....
之后在工作中遇到各种“坑”都会及时更新的,也欢迎各位分享自己遇到的“奇葩事”~