antd-modal,你们都是咋优化的?

3,291 阅读2分钟

前言

在后台项目的开发中,相信我们经常会遇到以下的页面,一个table,一些search的查询,然后增删改查,有没有熟悉的味道,算是很标准页面了。

看到这种页面的我们,应该很开心吧。。会觉得很轻松,能撸起袖子一把嗦。

现状

由于基于antd-pro-v5的项目,大致代码如下。

import React, { useRef, useState } from 'react';

import { PageContainer } from '@ant-design/pro-layout';

import type { ProColumns, ActionType } from '@ant-design/pro-table';

import ProTable from '@ant-design/pro-table';

import { Button } from 'antd';

import type { FormInstance } from 'antd/lib/form';

import type { ChannelListItem } from './data.d';

import {

  DelModal,

  ShowDetailModal,

  ShowQRModal,

  AddChannelModal,

  ForbidModal,

} from '../components';

import { queryChannel } from './service';



const ChannelList: React.FC = () => {

  const formRef = useRef<FormInstance>();

  const actionRef = useRef<ActionType>();



  // 添加渠道弹窗

  const [addVisible, handleAddVisible] = useState<boolean>(false);

  // 删除弹窗

  const [delVisible, handleDelVisible] = useState<boolean>(false);

  // 禁用弹窗

  const [forbidVisible, handleForbidVisible] = useState<boolean>(false);

  // 查看按钮

  const [showDetailVisible, handleShowDetailVisible] = useState<boolean>(false);

  // 二维码弹窗

  const [qrVisible, handleQRVisible] = useState<boolean>(false);

  

  //设置当前Row

  const [curRow,setCurRow] = useState<ChannelListItem>({});



  //列表项....

  const columns: ProColumns<ChannelListItem>[] = [];



  return (

    <>

      <PageContainer>

        <ProTable<ChannelListItem>

          actionRef={actionRef}

          formRef={formRef}

          scroll={{ x: 'max-content' }}

          rowKey="id"

          request={(params) => queryChannel(params)}

          columns={columns}

          dateFormatter="string"

          toolBarRender={() => {

            return [

              <Button

                key="button"

                type="primary"

                onClick={() => {

                  handleAddVisible(true);

                }}

              >

                添加渠道

              </Button>,

            ];

          }}

        />

      </PageContainer>

      {/* 添加渠道弹窗 */}

      <AddChannelModal

        visible ={addVisible}

        onOk={ () => {

          handleAddVisible(false);

        }}

        onCancel={() => {

          handleAddVisible(false);

        }}

      />



      {/* 删除弹窗 */}

      <DelModal

        visible ={delVisible}

        onOk={ () => {

          handleDelVisible(false);

        }}

        onCancel={() => {

          handleDelVisible(false);

        }}

      />

      {/* 禁用弹窗 */}

      <ForbidModal

        visible ={forbidVisible}

        onOk={ () => {

          handleForbidVisible(false);

        }}

        onCancel={() => {

          handleForbidVisible(false);

        }}

      />

      {/* 显示详情弹窗 */}

      <ShowDetailModal

        visible={showDetailVisible}

        onOk={ () => {

          handleShowDetailVisible(false);

           //其他和父组件中的一些交互,比如操作完弹窗,刷新列表,当然一般详情展示不需要刷新列表,此处就是为了举个例子

           actionRef?.current?.reload();//刷新table列表

        }}

        title={`查看${curRow.channelName}`}

        onCancel={() => {

          handleShowDetailVisible(false);

        }}

        curRow={curRow}

      />

      {/* 获取二维码弹窗 */}

      <ShowQRModal

        visible ={qrVisible}

        onOk={async () => {

          handleQRVisible(false);

        }}

        onCancel={() => {

          handleQRVisible(false);

        }}

      />

    </>

  );

};



export default React.memo(ChannelList);

举例一个ShowDetailModal

import React from 'react';

import { Modal } from 'antd';



interface ShowDetailModalProps {

  visible: boolean;

  onCancel: () => void;

  onOk: () => void;

  curRow: Record<string, any>;

  title:string;

}



const ShowDetailModal: React.FC< ShowDetailModalProps> = ( { visible, onCancel, onOk, curRow,title }) => {

  return (

    <Modal

      destroyOnClose

      centered

      title={title}

      visible={visible}

      onCancel={() => onCancel()}

      onOk={ ()=> onOk()}

    >

     <>内容....{curRow.balabal...}</>

    </Modal>

  );

};

export default ShowDetailModal;

是不是像在你工位装了摄像头?【狗头】

因为这是我写得【仙女哭泣】

看了上述代码,有能优化的点吗?

有有有,这个我会,比如【禁用】,【删除】等此类的,简单的弹窗交互的,会更倾向于使用类似于 Modal.confirm 等 API 直接调用弹出就好了。少写两个modal。

那剩下三个呢?


个人认为,Modal.confirm这种形式,不止是为了减少页面的modal组件数。也有一些在性能上减少反复渲染。

为什么?

从我们的代码,可以想到两个问题

需要为多个modal,分别设置visible,handleVisible;

这些modal都需要写在render里面,每次触发setState,用来显示,以及传入的数据,很多都是当前的列表对应row的data,需要setCurRow(),都会触发其他兄弟组件反复渲染。

(其他兄弟组件这个时候用React.memo,React.PureComponent,作用就微弱的体现了。)

ps:为啥是微弱呢?React.memo和React.PureComponent都是对传入的props进行浅比较。如果是props是对象的话,也是会重复渲染的。需要额外在钩子里写判断,懂得都懂

思路

抽取公共部分逻辑,visible, handleVisible

让组件自己维护visible,也能避免写在render里的其他组件反复渲染

然后就开始了———

性能高一点的modal

通过refs控制,实现子modal组件内控制visible

import React,{ forwardRef, useImperativeHandle} from 'react';

import { Modal } from 'antd';



interface ShowDetailModalProps {

  visible: boolean;

   // onCancel: () => void;  改动————通过高阶组件维护

  onOk: () => void;

  curRow: Record<string, any>;

  title?:string;

}



const ShowDetailModal: React.FC< ShowDetailModalProps> = ( { modalVisible, close,onOk, curRow,title },ref) => {

  //自己状态维护visible

  const [visible, handleModalVisible] = useState(false);

  const modalShow = () => {

    handleModalVisible(true);

  };

  const modalHide = () => {

    handleModalVisible(false);

  };

  //暴露方法给父组件

  useImperativeHandle(ref, () => ({

    modalShow,

    modalHide,

  }));

  return (

    <Modal

      destroyOnClose

      centered

      title={title}

      visible={visible}

      //onCancel={() => onCancel()}

      onCancel={

          ()=>{

             modalHide();

          }

      }

      onOk={ ()=> {

        onOk();

        modalHide();

      }}

    >

     <>内容....{curRow.blabla}</>

    </Modal>

  );

};

export default forwardRef(ShowDetailModal);

父组件

import React, { useRef, useState } from 'react';

import { PageContainer } from '@ant-design/pro-layout';

import type { ProColumns, ActionType } from '@ant-design/pro-table';

import ProTable from '@ant-design/pro-table';

import { Button } from 'antd';

import type { FormInstance } from 'antd/lib/form';

import type { ChannelListItem } from './data.d';

import { ShowDetailModal} from '../components';

import { queryChannel } from './service';

import optimizationModalWrapper from 'xxxx';



const ChannelList: React.FC = () => {

  const formRef = useRef<FormInstance>();

  const actionRef = useRef<ActionType>();



  // 查看按钮 删除

 const [showDetailVisible, handleShowDetailVisible] = useState<boolean>(false);



  const [curRow,setCurRow] = useState<ChannelListItem>({});

  //新增

  const showDetailRef = useRef();

  //列表项....

  const columns: ProColumns<ChannelListItem>[] = [

  ...,

  {

   title: '操作',

      dataIndex: 'option',

      valueType: 'option',

      fixed: 'right',

      width: 80,

      render: (_, record) => (

        <>

        //通过ref直接调用组件modalShow,实现控制显示

          <a onClick={() => {showDetailRef?.current?.modalShow()}}>查看详情</a>

        </>

      ),

  }

  ];

  return (

    <>

      <PageContainer>

        <ProTable<ChannelListItem>

          actionRef={actionRef}

          formRef={formRef}

          scroll={{ x: 'max-content' }}

          rowKey="id"

          request={(params) => queryChannel(params)}

          columns={columns}

          dateFormatter="string"

        />

      </PageContainer>



      {/* 显示详情弹窗 */}

      <ShowDetailModal

        ref={showDetailRef}//增加

        onOk={async () => {

          handleShowDetailVisible(false);

        }}

        onCancel={() => {

          handleShowDetailVisible(false);

          //改为

          showDetailRef?.current?.modalHide()

        }}

        curRow={curRow}

      />

     

    </>

  );

};



export default React.memo(ChannelList);

这种方式就是通过ref来控制子组件,自己的状态。能减少一些setState触发的渲染,有一些优化。

但是对于,modal组件内的需要的行数据,依旧是需要通过setCurRow,然后通过props传入。频繁操作,还是会反复触发其他组件的渲染,所以还要继续优化。

HOC抽取公共部分

抽取公共部分我们可以采用高阶组件的方式

高阶组件

高阶组件(HOC):简单来说,高阶组件就是接受一个组件作为参数并返回一个新组件(功能增强的组件)的函数。

注意:高阶组件是一个函数,并不是组件;

通用的维护modal,参考(伸手)了下,antd源码中,Modal.confirm代码(源码地址,有需要自取),然后改了改。

注意:下面是重点

import type { ComponentClass, FunctionComponent } from 'react';

import React from 'react';

import ReactDOM from 'react-dom';



type ComponentTypes = string | FunctionComponent<{ close: () => void; visible: boolean; }> | ComponentClass<{ close: () => void; visible: boolean; }, any>;



const optimizationModalWrapper = (component: ComponentTypes) => {

  const container = document.createDocumentFragment();

  const destroy = () => {

     //删除挂载的React组件

      ReactDOM.unmountComponentAtNode(container);

    }

  // 渲染组件

  const render = ( restProps: Record<string,any>) => {

   //将modal组件内容挂载到fragement上,添加一些close,visible公共的属性

    const comModalVDom = React.createElement(component, {

      ...restProps,

      close: destroy,

      visible: true

    })

    //将统一处理添加close和visible的属性,挂载在container上

    ReactDOM.render(comModalVDom, container )

  }



  return (restProps: Record<string,any>) => {

    render(restProps);

  }

};



//这里的主要目的就是,统一处理了下modal框自己的显示和隐藏。

//其他的modal需要的数据或者属性,利用函数调用的方式,通过restProps传入

export default optimizationModalWrapper;

然后我们的代码

import React, { useRef, useState } from 'react';

import { PageContainer } from '@ant-design/pro-layout';

import type { ProColumns, ActionType } from '@ant-design/pro-table';

import ProTable from '@ant-design/pro-table';

import { Button } from 'antd';

import type { FormInstance } from 'antd/lib/form';

import type { ChannelListItem } from './data.d';

import { ShowDetailModal} from '../components';

import { queryChannel } from './service';

import optimizationModalWrapper from 'xxxx';



const ChannelList: React.FC = () => {

  const formRef = useRef<FormInstance>();

  const actionRef = useRef<ActionType>();



  // 查看按钮 删除

 const [showDetailVisible, handleShowDetailVisible] = useState<boolean>(false);



  //设置当前Row 删除

  const [curRow,setCurRow] = useState<ChannelListItem>({});



  //列表项....

  const columns: ProColumns<ChannelListItem>[] = [

  ...,

  {

   title: '操作',

      dataIndex: 'option',

      valueType: 'option',

      fixed: 'right',

      width: 80,

      render: (_, record) => (

        <>

      //回顾我们高阶组件的返回也是一个函数

      // return function (restProps: Record<string,any>) {

      //  render({component, restProps});

      //  }

      //optimizationModalWrapper(组件)(restProps)

     //不用setState,curRow,导致页面反复渲染 

     //optimizationModalWrapper(ShowDetailModal)(restPorps)

          <a onClick={() => optimizationModalWrapper(ShowDetailModal)({

           curRow:record,

           onOK: () => {

              handleShowDetailVisible(false);

              actionRef?.current?.reload();//刷新table列表

           },

           title:`查看${record.channelName}`

        })}>查看详情</a>

        </>

      ),

  }

  ];

  return (

    <>

      <PageContainer>

        <ProTable<ChannelListItem>

          actionRef={actionRef}

          formRef={formRef}

          scroll={{ x: 'max-content' }}

          rowKey="id"

          request={(params) => queryChannel(params)}

          columns={columns}

          dateFormatter="string"

        />

      </PageContainer>



      {/* 显示详情弹窗 删除,其他弹窗也可以删除*/}

  

     

    </>

  );

};



export default React.memo(ChannelList);

然后ShowDetailModal改动一点点

import React from 'react';

import { Modal } from 'antd';



interface ShowDetailModalProps {

  visible: boolean;

  close:()=>void; //改动———

   // onCancel: () => void;  改动————通过高阶组件维护

  onOk: () => void;

  curRow: Record<string, any>;

  title?:string;

}



const ShowDetailModal: React.FC< ShowDetailModalProps> = ( { visible, close,onOk, curRow,title }) => {

  return (

    <Modal

      destroyOnClose

      centered

      title={title}

      visible={visible}

      //onCancel={() => onCancel()}

      onCancel={()=> close()}//改动————高阶组件传入的close

      onOk={ ()=> {

        onOk();

        close()//改动———关闭弹窗

      }}

    >

     <>内容....{curRow.blabla}</>

    </Modal>

  );

};

export default forwardRef(ShowDetailModal);

其他子Modal也可以参考这种方式实现了。 这样我们的业务逻辑在代码层面上不仅清爽了很多,性能理论上也是有所优化。。毕竟体验上讲真的,倒是没有很大的感受。(公司的电脑还是太好了)【狗头】

小伙伴们也可以在react项目中试试,项目中有用了proComponents中ModalForm的,也能用这种方式稍加修改。中国人不骗中国人,本人亲测有效。

一点疑问

hooks是组件的封装,高阶函数也是功能增强的组件,两者区别是啥?

是不是也能利用hooks稍加封装,然后实现通用逻辑。

一点疑问留给各位..

结束

就突兀的结束了,辛苦各位摸鱼的小伙伴看到这里,希望以上对大家有所帮助。。