前言
在后台项目的开发中,相信我们经常会遇到以下的页面,一个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稍加封装,然后实现通用逻辑。
一点疑问留给各位..
结束
就突兀的结束了,辛苦各位摸鱼的小伙伴看到这里,希望以上对大家有所帮助。。