大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福无处不在
前言
本文是屎山代码优化系列的第二篇文章,本文将为大家提供开发并实现一个Pop组件的核心思路
头脑风暴 ?
- 首先,弹窗的功能antd已经为我们提供了,因此即使是定式化的ui,我个人也不建议从0到1开发,而是应该使用gloal去覆盖重写,故我们这里借助antd的Modal来实现功能
- 从布局上来讲一个弹窗可以被分为头部、主体、底部三部分
1.头部一般由标题和关闭按钮组成,而标题部分应该是可变的
2.主体一般都是一个或者多个form表单用于收集或者展示用户信息,因此,form表单应该被内置,而form表单的每一项则应该作为动态的值由外部控制
3.底部一般只会包括一个或多个操作按钮,这一部分ant的Modal已经给我们提供了定制化属性我们只需要进行透传就可以了
- 至于命名空间,其实就是一个一个的表单,它们相互独立互不干扰,使用map或者数组进行管理即可
代码实现
props定义
结合实际开发业务需求最终设计出的props如下
interface Ipop {
visible: boolean;
title: string;
columns?: TformItem[];
initialValue?: Record<string, any> | Record<string, any>[];
width: number;
height?: number;
labelCol?: {
span?: number;
[key: string]: any;
};
prefixCls?: string;
modalConfig?: any;
multiTitle?: string;
restComponent?: React.ReactNode;
outComponet?: (
formIndex: number,
initialValue: any | any[]
) => React.ReactNode;
onOk?: (values: any) => boolean | Promise<any>;
onCancel?: (
clickType?: boolean,
values?: Record<string, any> | any[]
) => void;
}
- visible
用于控制Pop组件的显示或隐藏
- title
表单标题
- columns
具体的表单项数组,考虑到不同命名空间下的key理论上不可能重复,我们这里设计的是一个一维数组
type TformItem = {
label: string;
name: string;
component?: React.ReactNode;
rules?: any[];
};
- initialValue
考虑到编辑表单初始值回填和命名空间的问题,我们这里将其设计为数组对象或纯对象
- multiTitle
在我们的业务中,每一个命名空间下的表单都有一个小标题,它们拥有着共同的前缀,但是使用数字1,2,3进行递增
- width、height
用于控制表单的宽高
- prefixCls
为客制化样式提供类名标记
- modalConfig
其他的antd的Modal的支持属性
- restComponent
在表单项之外的任意项
- outComponet
表单项的具体渲染内容
- onOk和onCancel
确认和取消按钮的回调
代码实现
第一步就是根据initialValue参数的类型进行对初始值设置,包括弹窗关闭的重置处理,如下,分别从表单对应调用resetFields或setFieldsValue即可
useEffect(() => {
if (!visible) {
if (Array.isArray(formRef.current)) {
formRef.current.forEach((v) => {
v?.resetFields();
});
}
} else {
if (initialValue) {
if (Array.isArray(initialValue)) {
initialValue.forEach((v, i) => {
if (
Object.prototype.toString.call(v) === "[object Object]" &&
Object.keys(v).length
) {
queueMicrotask(() => {
formRef.current[i]?.setFieldsValue(v);
});
}
});
} else {
if (
Object.prototype.toString.call(initialValue) ===
"[object Object]" &&
Object.keys(initialValue).length
) {
queueMicrotask(() => {
formRef.current[0]?.setFieldsValue(initialValue);
});
}
}
}
}
}, [visible, initialValue]);
第二步是处理表单的渲染
由于有命名空间的概念,而initialValue为数组时恰好与命名空间一一对应,故我们对initialValue进行遍历即可
const renderForm = () => {
if (Array.isArray(initialValue)) {
return initialValue.map((_, i) => {
return (
<>
<div className={styles.pop_multiTitle}>
{multiTitle}
{i + 1}
</div>
{_renderForm(i)}
{i != initialValue.length - 1 ? (
<div className={styles.pop_line} />
) : null}
</>
);
});
}
return _renderForm(0);
};
而_renderForm即是简单的生成一个antd的Form组件
const _renderForm = (i: number) => {
return (
<Form
labelCol={labelCol}
onFinish={handleOk}
labelAlign="left"
form={form[i]}
layout="horizontal"
colon={false}
ref={(form) => (formRef.current[i] = form)}
>
{renderItem(columns!, false, i)}
</Form>
);
};
最后renderItem则处理每一个表单项的创建,主要是对不同分支的判断处理,由于我们支持一行一个或者一个多个的情况,所以还会有分组的能力,比如isGroup则是为此单独打的类名标签
const renderItem = (arr: any[], isGroup?: boolean, formIndex?: number) => {
return arr.map((v, i) => {
if (Array.isArray(v)) {
return (
<div className={styles.pop_group} key={i}>
{renderItem(v, true, formIndex)}
</div>
);
}
const klass = classNames({
[styles.pop_group_item]: isGroup,
[`${prefixCls ? prefixCls : ""}`]: true,
[`${prefixCls ? prefixCls + "_" : ""}pop_group_item_${i}`]: isGroup,
});
return (
<Form.Item
labelCol={v.labelCol}
rules={v.rules}
label={v.label === "$null$" ? " " : v.label}
name={v.name}
key={v.name}
className={klass}
>
{typeof outComponet === "function" && v.name === "$useOutComponent$"
? outComponet(formIndex!, initialValue)
: v.component}
</Form.Item>
);
});
};
最后处理下新增和取消按钮,它们很简单,就是获取到外部传入的接口进行调用即可
const handleCancel = (e?: any) => {
const isClickBtn = e?.pageY > 500;
if (typeof onCancel === "function") {
exexLoading();
onCancel(isClickBtn, getFormValues());
}
};
使用示例
引入并按照相关的参数进行设置
<Pop
...
/>
比如当需要进行编辑预览时,可以使用useState来进行状态管理
const [initialValue, setInitialValue] = useState<any>({});
然后在获取到对应的预览内容后进行设置即可
selectPermit(params).then((res: any) => {
...
setInitialValue({
...da,
auditId: list[0]?.id,
});
...
});
总结
接下来,笔者会继续分享关于多级Table的封装与实现思路,还有利用webpack-loader对面包屑进行调用层面的优化,另外,笔者已经很久没有主力开发过react了,因此对于react的技术储备更新不及时,接下来打算对react的新的api或能力进行学习。感兴趣的可以关注笔者,我们一起学习一起进步?
如果本文对您有用,希望能得到您的点赞和收藏
订阅专栏,每周更新2-3篇类型体操,每月1-3篇vue3源码解析,等你哟?