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的
  • 异步获取数据后根据数据渲染了表单后怎么赋值在重新渲染之后
相关推荐
FinGet9 小时前
那总结下来,react就是落后了
前端·react.js
王解12 小时前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
AIoT科技物语1 天前
免费,基于React + ECharts 国产开源 IoT 物联网 Web 可视化数据大屏
前端·物联网·react.js·开源·echarts
初遇你时动了情1 天前
react 18 react-router-dom V6 路由传参的几种方式
react.js·typescript·react-router
番茄小酱0011 天前
ReactNative中实现图片保存到手机相册
react native·react.js·智能手机
王解1 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
小牛itbull1 天前
ReactPress—基于React的免费开源博客&CMS内容管理系统
前端·react.js·开源·reactpress
~甲壳虫1 天前
react中得类组件和函数组件有啥区别,怎么理解这两个函数
前端·react.js·前端框架
用户8185216881172 天前
react项目搭建create-router-dom,redux详细解说
react.js
new Vue()2 天前
Vue vs React:两大前端框架的区别解析
vue.js·react.js·前端框架