react使用antd-useWatch的坑(react太难了)

错误总结:

  • 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的
  • 异步获取数据后根据数据渲染了表单后怎么赋值在重新渲染之后
相关推荐
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
疯狂的沙粒2 小时前
如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!
react.js·typescript
鑫宝Code3 小时前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
沉默璇年12 小时前
react中useMemo的使用场景
前端·react.js·前端框架
红绿鲤鱼13 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
loey_ln15 小时前
FIber + webWorker
javascript·react.js
zhenryx16 小时前
前端-react(class组件和Hooks)
前端·react.js·前端框架
老码沉思录20 小时前
React Native 全栈开发实战班 - 性能与调试之打包与发布
javascript·react native·react.js
沉默璇年1 天前
react中Fragment的使用场景
前端·react.js·前端框架
GISer_Jing1 天前
React渲染流程与更新diff算法
前端·javascript·react.js