从一个表格render方法问题看React函数组件的更新
最近在开发中碰到了一个现象觉得很有典型能作为例子所以给大家分享一下,从这个现象我们能很清楚的看到函数组件的更新的特点,以及我们应该如何去理解和拥抱函数组件。
问题的产生
需求
假设我们现在有两个表格,这里我们称之为A表和B表,这两个表的切换通过用户的点击触发,这里我们使用antd的table组件去实现这个表格。我们其实有两种思路,第一种就是我们动态切换表格的列配置和数据源,第二种是创建两个固定的表格,我们通过一个值来切换这两个的切换。这里我使用到是第一种方式。
实现
首先我们需要创建数组作为表格的两种column配置
const columns1 = [{title:'姓名',dataIndex:'name',key:"name"},{title:'性别',dataIndex:'sex',key:"sex"}]
const columns2 = [{...}...]
const data1 = [{id:1,name:'张珊',sex:"女"},{id:2,name:'李斯',sex:"男"},{id:3,name:'王五',sex:"男"}]
const data2 = [{...}...]
上面我举了很简单的两个例子,我们创建了两个列配置,那接下来我们需要一个state来判断当前需要使用的列配置,这里可以直接用state保存配置也可通过state来判断使用哪个配置。这里我们直接使用state保存列配置,那么table组件对应的使用应该如下所示:
const [currentColumns,setCurrentColumns ] = useState([])
return (
.
<Table dataSource={dataSource} columns={currentColumns} />;
.
)
然后我们使用的时候直接动态的setCurrentColumns
就可以了,数据源的更改和列配置是一样的。
问题
本来上面的方法没有任何问题,但是假设A表的配置使用的是columns1
,后端给你返回的性别字段并不是文字而是数字,这个时候你就需要通数据字典去对应的显示性别,当然我们可以规定1为性别男,2为性别女,但是这是很理想的状态。如果我们性别这一列还需要跨性别等一系列特殊现象,并且这个数据字典需要我们去请求接口获取,那么我能当然的想到应该如下去做
const [allSex,setAllSex]=useState({})
useEffect(()=>{
request().then(res=>{
setAllSex(res)
})
},[])
我们可以请求好对应的数据然后使用state保存下来供后面表格渲染使用,所以我们列配置也需要适当的变化一下:
const columns1 = [
{title:'姓名',dataIndex:'name',key:"name"},
{title:'性别',dataIndex:'sex',key:"sex",ender: (_, record) => {
return allSex[record.sex]
}}
]
到这里就基本写完了,但是你在测试的时候会发现性别这一栏是空的,通过排查你会发现render函数其实拿到的allSex是一个空对象。
问题分析
首先我们可以通过控制看到我们网络请求是否已经完成并且是否有值,在得到确定的信息后我们可以排除是请求的问题。其次排查代码逻辑发现并没有问题,这个时候我就想到了一个前端人耳熟能详的词——闭包。
我们都知道函数组件的本质就是函数,每个函数都有其独立的作用域,当我们每次更新state的值的时候,其实就是相当于重新创建了一个state,所以这也是为什么我们只能在useEffect
中拿到更新后的state值的原因。所以我们可以联想到,虽然请求已经完成state更新了,但是表格的column的render函数拿到的其实是一开始定义的初始值,当state更新后函数组件已经是一个全新的函数了,所以最开始的column是无法拿到更新后的state的值的,拿到的只能是定义state的初始值。
解决
解决的办法很简单,我们使用一个useEffect函数,他的依赖是allSex
,然后我们在这个useEffect中去调用setCurrentColumns的方法。
useEffect(()=>{
setCurrentColumns(columns1)
},[allSex])
当allSex被赋值时会触发这个影响函数,此时我们调用setCurrentColumns,配置项里的render函数拿到的就是最新的allSex也就是请求成功后的allSex,这个时候表格就能正常渲染了。