错误总结:
- useWatch的值是在react下个生命周期才会生效
- setState的时候才会重新渲染!setState的时候才会重新渲染!setState的时候才会重新渲染!(记得但是永远开发的时候忘)
- 还有因为Event Loop、宏任务、微任务
求助:写的有点痛苦想看看jym在编写表单的时候
- 详情或者编辑的情况是如何触发select之类的change的
- 异步获取数据后根据数据渲染了表单后怎么赋值在重新渲染之后
场景一
正常的情况,我有一个列表,查看详情,正常切换了Modal的内容,aliyun情况下上有信息有参量参数可编辑表格,其他情况没有
JavaScript
// 有一个监听
const clausePlatformType = Form.useWatch('clausePlatformType', {
form: serverBaseFormRef.current,
});
// 详情按钮
<a
key="view"
onClick={() => {
setFormData(formRefs, record);
setModalVisible(true);
}}
>
{intl.formatMessage({
id: 'table.operate.view',
defaultMessage: '查看详情',
})}
</a>,
javascript
// setFormData
export const setFormData = (refs: any, data: any) => {
const { serverBaseFormRef, serverRouteFormRef, serverUpstreamFormRef, serverPlugTableFormRef } =
refs;
serverBaseFormRef.current?.setFieldValue('clausePlatformType', data.service.platformType);
setTimeout(() => {
serverBaseFormRef.current?.setFieldsValue({
// 设置内容值
});
serverRouteFormRef.current?.setFieldsValue({
// 设置内容值
});
serverUpstreamFormRef.current?.setFieldsValue({
// 设置内容值
});
serverPlugTableFormRef.current?.setFieldValue('table', data.plugins);
}, 0);
};
ini
<Modal>
<Card
title={服务-基本信息}
>
<ServerBaseForm readonly={true} formRefProp={serverBaseFormRef} />
</Card>
<Card
style={cardStyle}
title={服务-路由信息}
>
{clausePlatformType === 'ALIYUN' ? (
<ServerRouteFormALIYUN readonly={true} formRefProp={serverRouteFormRef} />
) : (
<ServerRouteFormIDC readonly={true} formRefProp={serverRouteFormRef} />
)}
</Card>
<Card
title={服务-上游服务信息}
>
{clausePlatformType === 'ALIYUN' ? (
<ServerUpstreamFormALIYUN readonly={true} formRefProp={serverUpstreamFormRef} />
) : (
<ServerUpstreamFormIDC readonly={true} formRefProp={serverUpstreamFormRef} />
)}
</Card>
<Card
style={cardStyle}
title={服务-策略插件}
>
<ServerPlugTableForm readonly={true} formRefProp={serverPlugTableFormRef} />
</Card>
</Modal>
这里因为先设置完值之后执行了setModalVisible,进入了下一个react的render,所以一切正常。
场景二
前提:因为在分析出问题之前,自以为useWatch会在setFieldValue的时候更新,进行正常渲染的切换并赋值
错误结果为:正确切换了对应的表单,但是切换的表单没有赋值
点击详情按钮打开Modal,进入页面后调用info接口获取数据
2.1 错误截图
2.2错误时代码
其他一样 主要是赋值的地方的差别
scss
// 监听选择阿里云还是IDC
const clausePlatformType = Form.useWatch('clausePlatformType', {
form: serverBaseFormRef.current,
});
useEffect(() => {
if (modalVisible) {
setModalLoading(true);
setButtonLoading(true);
getServiceInfo(props.infoId)
.then((data) => {
serverBaseFormRef.current?.setFieldValue('clausePlatformType', data.service.platformType);
setTimeout(() => {
serverBaseFormRef.current?.setFieldsValue({
// 设置内容值
});
serverRouteFormRef.current?.setFieldsValue({
// 设置内容值
});
serverUpstreamFormRef.current?.setFieldsValue({
// 设置内容值
});
}, 0);
})
.finally(() => {
setModalLoading(false);
setButtonLoading(false);
});
}
}, [modalVisible]);
错误分析:
javascript
Promise.resolve().then(()=>{
console.log(1)
setTimeout(()=>{
console.log(2)
})
}).finally(()=>{
console.log(3)
})
// 1 3 2
可以看出这段代码的执行顺序是:微任务.then->微任务.finally->宏任务setTimeout
所以我们在设置完表单后
kotlin
serverBaseFormRef.current?.setFieldValue('clausePlatformType', data.service.platformType);
执行了set进入了下一个渲染的周期,表单就会重新渲染,然后我们在设置setTimeout导致页面重新渲染,然后执行赋值的操作。
然后来细分一下后续的流程,我们在设置表单clausePlatformType后,我们推进了一个宏任务setTimeout进入Event Queue,时间为0ms,然后执行finally的两个setState的任务,react也是异步的,所以也是继续推入Event Queue,然后我们先执行setTimeout的任务,xxxxformRef们还是绑定旧的表单,然后设置值,接着执行两个setState,这时react进入新的render,所以渲染成ALIYUN该展示的表单,产生了这个问题。
解决方式:
- 一开始的结论说到setState才会触发react的渲染,一开始先入为主的以为了useWatch会监听,所以导致的问题,所以第一种方案就是把useForm改成setState的形式
- 增加setTimeout的延迟时间哪怕是1,执行顺序依旧是执行setTimeout,但是看到时间还没到,继而执行setModalLoading和setButtonLoading,然后再执行setTImeout的回调
正确的展示
场景三
详情页面,进入页面跳转到详情页,请求数据后渲染,数据赋值,页面展示错误的情况
ini
const clausePlatformType = Form.useWatch('clausePlatformType', {
form: serverBaseFormRef.current,
});
// 完全无法使用
useEffect(() => {
console.log(clausePlatformType);
}, [clausePlatformType]);
useEffect(() => {
const serviceId = searchParams.get('serviceId');
const showUpstreamForm = searchParams.get('showUpstreamForm');
if (!isEmpty(showUpstreamForm) && showUpstreamForm === 'false') {
setShowUpstreamForm(false);
}
if (serviceId) {
getServiceInfo(serviceId).then((data) => {
serverBaseFormRef.current?.setFieldValue('clausePlatformType', data.service.platformType);
setTimeout(() => {
serverBaseFormRef.current?.setFieldsValue({
// 设置内容值
});
serverRouteFormRef.current?.setFieldsValue({
// 设置内容值
});
serverUpstreamFormRef.current?.setFieldsValue({
// 设置内容值
});
});
});
}
}, []);
当解决场景2的时候,还以为修改setTimeout的延迟时间,就能一样解决,当时还没有想的这么透彻,没去认真想事件循环的顺序一步一步的来排,一直错误的以为是setTimeout绑定了旧的ref导致的,但是其实不然这是一个简单的例子:
javascript
let a =1
setTimeout(()=>{console.log(a);a=2},100)
setTimeout(()=>{console.log(a)},100)
// 1 2
然后真实的原因就是就是场景2分析的那样,这里的代码没有没有setState的任何操作,所以不会更新。
其实场景3陷入卡太久就是因为大脑陷入了死循环以为setFieldValue和useWatch会绑定触发react的渲染。
再提一嘴:写的有点痛苦想看看jym在编写表单的时候
- 详情或者编辑的情况是如何触发select之类的change的
- 异步获取数据后根据数据渲染了表单后怎么赋值在重新渲染之后