为什么在校验前动态生成的Loading,没有及时渲染到页面

为什么在校验前动态生成的Loading,没有及时渲染到页面

背景

  • 通常,我们进行保存时,需要先对表单数据进行前端校验,校验通过后,才能调用接口去保存数据;
  • 然后Loading组件是动态创建的dom,创建后渲染到页面上;
  • 一般我们都是在请求接口时进行loading,这样是没有问题的,但是当在校验的前面进行loading时,就有问题

问题描述

表单页面进行保存时,需要先对表单数据进行校验,当数据量很大时,校验不可避免地花费大量的时间,此时,我们希望Loading可以在开始前端校验时弹出,代码如下,代码逻辑没有问题,但是运行时会发现,loading并没有按照预期在开始前端校验时弹出,而是在校验完毕后,调用后端接口时弹出;

/**
 * todo 校验失败自动弹窗的 公共的校验
 * @param dataSets (DataSet | DataSet[]) 数据源或者数据源集合
 * @param needValidateNoEdit (boolean) 可选参数,数据源是否需要校验没有编辑的状态,默认为true,需要校验是否有编辑
 * @returns (boolean) 校验结果
 * * 使用案例:import { comValidateMsg } from '@common/utils/comMethod/comValidate';
 * basicInfoHeader['_nameMeaning'] = '出口合同的基本信息';
 * const pass: boolean = await comValidateMsg(basicInfoHeader, !nextFlag); //* 点击【下一步】时,不校验「未存在新增或者修改操作!」
 * if (!pass) { //* 校验失败时
 *   return false;
 * }
 */
const comValidateMsg = async (dataSet: DataSet | DataSet[], needValidateNoEdit: boolean = true) => {
  const hideLoading = showLoading({tip: '数据校验中,请稍等...'})
  const message : string | React.ReactNode = await comValidate(dataSet, needValidateNoEdit);
  hideLoading();
  if (message) { //* 校验失败时
    commonNotification('warning', message);
    return false;
  }
  return true;
};

Loading组件代码附在本页最下方

问题分析:

一开始怀疑的时Loading中肯定有哪一个地方是异步的,最大的可能就是ReactDOM.render,但是通过async/await也没有解决,然后通过搜索发现ReactDOM.render,有第三个参数,第三个参数是一个回调函数,将在渲染完成后调用;但是依旧没有解决这个问题!!!

调试的过程中,在loading之后校验之前打端点,发现loading组件可以正常渲染出来;于是想在loading之后设置个定时器,等个50ms再继续执行前端校验,发现有效果,Loading可以正常渲染出来,后面发现等待1ms也有效果;

但是如果在这1ms内一直操作dom(执行js),loading也不会被渲染出来;

如果在axios之前进行loading时,也是正常的,loading可以渲染出来;

/**
 * todo 校验失败自动弹窗的 公共的校验
 * @param dataSets (DataSet | DataSet[]) 数据源或者数据源集合
 * @param needValidateNoEdit (boolean) 可选参数,数据源是否需要校验没有编辑的状态,默认为true,需要校验是否有编辑
 * @returns (boolean) 校验结果
 * * 使用案例:import { comValidateMsg } from '@common/utils/comMethod/comValidate';
 * basicInfoHeader['_nameMeaning'] = '出口合同的基本信息';
 * const pass: boolean = await comValidateMsg(basicInfoHeader, !nextFlag); //* 点击【下一步】时,不校验「未存在新增或者修改操作!」
 * if (!pass) { //* 校验失败时
 *   return false;
 * }
 */
const comValidateMsg = async (dataSet: DataSet | DataSet[], needValidateNoEdit: boolean = true) => {
  const hideLoading = showLoading({tip: '数据校验中,请稍等...'})
  // await axios.get(
  //  `/hpfm/v1/${getCurrentOrganizationId()}/lovs/data?lovCode=HPFM.OA_WORKFLOW_ADDRESS`
  //);
  await new Promise((resolve) => setTimeout(() => resolve(''), 1)); //* JavaScript引擎线程暂停解析js代码,使GUI渲染线程进行渲染,将loading渲染到页面上
  const message : string | React.ReactNode = await comValidate(dataSet, needValidateNoEdit);
  hideLoading();
  if (message) { //* 校验失败时
    commonNotification('warning', message);
    return false;
  }
  return true;
};


### 原理分析

当我们打开浏览器的一个tab页面时,就会产生一个进程,这个进程中包含**GUI渲染线程****js引擎线程****事件触发线程****http请求线程****定时器触发线程**;当await 一个请求时,执行**http请求线程**,并不会阻止**GUI渲染线程**,所以Loading可以正常渲染出来;

然而**GUI渲染线程****js引擎线程**是互斥的,即当执行 *JS 引擎*线程时,并不会执行GUI渲染线程,只有当JS引擎线程的当前任务队列为空时,才会执行GUI渲染线程;所以dom操作是同步的,但是渲染却是异步的;

### Loading组件如下

```js
import React from 'react';
import ReactDOM from 'react-dom';
import { Spin } from 'choerodon-ui/pro';
import { Size } from 'choerodon-ui/lib/_util/enum';
import './loading.less'
import $ from 'jquery';

/**
 * zhy created Loading Component
 * 具体使用,
 * 1. import showLoading from '@common/utils/Loading';
 * 2. const hideLoading = showLoading({size: Size.small, tip: '加载中'});
 * 3. hideLoading();
 */
interface LoadingProp {
  size?: Size; //? 组件大小,可选值为 small default large,默认“large”
  tip?: string; //? 描述文案,默认“加载中,请稍等...”
}

const Loading: React.FC<LoadingProp> = (props) => {
  const { size = Size.large, tip = '加载中,请稍等...', ...otherProps} = props;

  const c7nIcon = ( //* 自定义指示符
    <span className="sc-grREDI eUEsqs c7n-spin-dot c7n-spin-initial-dot">
      <span />
      <span />
      <span />
      <span />
    </span>
  );
  return (
    <div className="c7n-spin-nested-loading" style={{'height': '100%'}}>
      <Spin indicator={c7nIcon} style={{'maxHeight': '100%'}} size={size} tip={tip} {...otherProps}/>
    </div>
  );
};

const showLoading = (props: LoadingProp = {}) => { //? 动态创建并展示loading效果
  const div = document.createElement('div');
  div.setAttribute('id', 'loadingContainer');
  div.setAttribute('class', 'pageLoading');
  ReactDOM.render(<Loading {...props}/>, div);
  document.querySelector('body')?.appendChild(div);
  //* loading时页面不允许滚动
  $(document.body).css({
    'overflow-x': 'hidden',
    'overflow-y': 'hidden'
  });

  const hide = () => { //? 卸载本loading组件
    if (document.querySelector('body > #loadingContainer')) { //* loading容器尚未被移除时
      document.querySelector('body')?.removeChild(div);//* 移除挂载的容器
      ReactDOM.unmountComponentAtNode(div); //* 从div中移除已挂载的Loading组件
      $(document.body).css({
        'overflow-x': 'auto',
        'overflow-y': 'auto'
      });
    }
  }
  return hide; //* 返回卸载函数
}

export default showLoading;

你可能感兴趣的:(javascript,前端)