跳到主要内容

useEffect异步渲染问题

问题复现

当进入页面时,点击添加表单的班级下拉框,发现没有班级,第二次点击的时候,才会出现班级的下拉列表:

  const [classroomList, setClassroomList] = useState<API.ClassroomVO[]>([]);
const initData = async () => {
const res = await queryAllClassroomByCurrentUser();
setClassroomList(res.data || []);
setLoading(false);
}
useEffect(() => {
initData()
}, [])

下面是Procomponent的表格
{
title: (
<>
班级
<Tooltip title="每个收集链接以班级为单位整合所需要收集的文件">
<QuestionCircleOutlined style={{marginLeft: 4}}/>
</Tooltip>
</>
),
dataIndex: 'classroomId',
valueType: 'select',
renderText: (_, record) => {
return classroomList.find(item => item.classroomId === record.classroomId)?.className;
},
request: async () => {
return !loading ? classroomList?.map(item => {
return {
label: item.className,
value: item.classroomId
}
}) : []; //数据还在加载中,返回空数组
},
formItemProps: {
rules: [{required: true, message: '请选择班级'}],
},
},

原因

可能的原因如下:

  1. 异步操作的时序问题

在 React 中,useEffect 是在组件渲染完成后异步执行的。如果你的组件依赖于异步获取的数据来渲染,那么在初次渲染时,数据可能尚未加载完成,导致下拉框的数据为空。

  1. 表单渲染与状态更新的顺序

当你打开页面时,组件会立即进行初次渲染,而 useEffect 中的异步操作需要时间完成。在此期间,组件可能会尝试渲染下拉框,但此时数据还未加载完毕。如果用户在这个时刻点击下拉框,数据尚未准备好,因此下拉框为空。

  1. 组件重渲染

如果数据在初次渲染后才加载完成,那么需要等待下一次组件重渲染以反映新数据。但如果没有触发重渲染的机制(如 state 更新),数据不会立即出现在下拉框中。

  1. request 的执行时机

如果你使用了 request 方法来加载数据,那么这个方法通常是在下拉框组件首次渲染时就执行的。如果此时数据还未准备好(例如,useEffect 中的异步请求还未完成),那么 request 方法会使用尚未加载的数据,导致下拉框为空。

  1. setState 的异步性

setState 是异步的,调用 setClassroomList 后,状态更新不会立即反映在 UI 上。如果在状态更新完成前组件重新渲染,可能会导致数据不一致。

  1. 缓存问题

如果在初次渲染时 classroomList 为空,而 request 方法依赖于这个空数据,那么缓存或未及时更新的状态可能会导致下拉框中没有数据。

可能的触发顺序:1.页面加载 -> 2. 组件初次渲染 -> 3. useEffect 开始异步请求数据 -> 4. 用户点击下拉框(此时 request 可能尚未拿到数据) -> 5. 数据加载完成,状态更新 -> 6. 组件重新渲染,下拉框数据正常

解决办法

不使用request:

{
title: (
<>
班级
<Tooltip title="每个收集链接以班级为单位整合所需要收集的文件">
<QuestionCircleOutlined style={{marginLeft: 4}}/>
</Tooltip>
</>
),
dataIndex: 'classroomId',
valueType: 'select',
renderText: (_, record) => {
return classroomList.find(item => item.classroomId === record.classroomId)?.className;
},
renderFormItem: (_, config, form) => {
return (
<Select
placeholder="请选择班级"
onChange={(value) => {
form.setFieldsValue({
classroomId: value
});
}}
>
{classroomList?.map(item => {
return (
<Select.Option key={item.classroomId} value={item.classroomId}>
{item.className}
</Select.Option>
);
})}
</Select>
);
},
}