屎山代码优化第一弹之Filter
大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福无处不在
前言
最近接手了一个前同事的项目,这个项目是标准的后台管理页面,每一个页面都由选择条件、表格、弹窗三部分组成。我觉得正常人肯定第一时间就会先把这三个部分抽出来作为公共组件的,但是当我看到代码时候,我是懵逼的😂
不过既然现在我接手了,那这种每个页面都重新写一遍的状态指定不能再发生了......
头脑风暴
首先,我们先理清楚一个筛选区域应该都有什么:
-
任意多个、任意类型的筛选条件(input、select等)
-
如果筛选条件过多应该怎么处理(是提供隐藏/展开按钮还是鼠标移入自动展开)
-
筛选条件是否可拖拽(拖拽交换位置还是任意拖放)
-
如果某个筛选条件设置了rbac权限的话,又应该怎么处理
接着,我们思考变与不变量:
- 变
1-页面中的筛选区域肯定是随着业务不同而不同的,因此这部分我们需要由外部定义
2-点击后的交互,比如具体查询的接口,也应该进行外置
- 不变
1-商业项目不应该花里胡哨,要尽量保证风格的统一,因此筛选区域的布局应当是不变的
2-提供的能力应当被内置,比如拖拽、查询重置的调用逻辑等
然后,我们结合业务进行分析:
事实上,不只是这一个系统,我们所有的系统都没有拖拽的需求(ps:可能是我们家产品不喜),因此,我们可以把拖拽往后放放先不进行实现(ps:毕竟提测上线是当下第一目标)
这个系统是没有展开和收起的,但是我们其他的系统是有的,所以这部分是需要在后期内置进去的
最后,关于本文:
由于时间因素,对于一些该有但是目前业务不需要的,我不会贴出来代码(ps:因为我没实现😂),只会分享下思路,感兴趣的也可以......,算了,万一分享其他地址被限流就不划算了
效果展示
代码实现
- 新建文件
我们在根文件创建components文件夹,并新建一个.tsx文件,至于名字嘛,你喜欢就好,不过也不要太随意,比如query.tsx、filter.tsx等,注意名字最好大写,因为react中组件名必须大写开头,当然,文件名并不必须要这样
- 将变量作为props接收
前文我们已经分析过有哪些变量了,这里我们就不再卖关子了,见如下propsType
interface Ifilter {
formItems: React.ReactNode[];
pageName: string;
loading?: boolean;
reSearchId?: number;
onReset?: () => void;
onSearch?: (values: Record<string,any>) => void;
onItemChange?: (values: Record<string,any>) => void;
}
1-formItems
它对应的就是我们页面的筛选条件,它是一个数组,每一个成员都是一个筛选条件,然后我在页面中获取并传递即可,至于示例的useFilterItem是我封的一个自定义hook,本质上还是在返回组件数组
const filterItems = useFilterItem(() => {
return {
feeds: selectFeeds,
business: selectBus,
};
}, [selectFeeds, selectBus]);
2-pageName
这是我预留的一个字段,用于区分不同的页面业务,虽然现在没啥用,但是将来,我可以根据它来绑定一个唯一的class以供定制化样式;又或者基于它做一些筛选条件的缓存处理等
3-loading
它是用来控制筛选期间查询按钮加载状态的,总不能允许用户一直点,你说是吧?
4-reSearchId
这是用于重新触发查询按钮的,比如我们在弹窗提交表单后,是需要重新获取接口的,这里其实就是利用useEffect重新提交了一次表单(ps:如果你愿意的话,通过ref也是可以的)
useEffect(() => {
form?.submit();
}, [reSearchId]);
5-onReset和onSearch
就名如其功能那样,当点击筛选或重置按钮的时候重新触发相关的逻辑,比如onReset长这样:它先重置了表单,接着调用外部的reset监听事件,最后重新提交以触发接口查询
const handleReset = () => {
form?.resetFields();
if (typeof onReset === "function") {
onReset();
}
const timer = setTimeout(() => {
form?.submit();
clearTimeout(timer);
}, 200);
};
6-onItemChange
其实,你不必像我这样提供一个专门的方法监听每一个筛选条件的改变,因为表单项本身就有对应的监听事件,比如Input的onChange等
可是,你有没有觉得,那样会让外部代码变得难以阅读嘛?而且也可能会造成内部无法管理对应的状态......
所以,我还是建议你像我一样这么做的,事实上,这很简单,因为我们使用的是Form表单,它天生给我们提供了onValuesChange事件,我们做的只是在此基础上将表单的ref引用一同抛出去即可
const handleValuesChange = (...rest:any[])=>{
if(typeof onItemChange === 'function'){
onItemChange({
changed:rest,
formRef:form
})
}
}
- 确认包裹元素
通过上边对props的解释,相信聪明的你已经知道了,它应该是一个Form表单
<Form
labelAlign="left"
form={form}
onFinish={hanldeSubmit}
onValuesChange={onItemChange}
layout="inline"
className={formKlass}
>
{formItems.map((v) => v)}
<div className={styles.filterBtns}>
<Form.Item wrapperCol={{ span: 16 }} key="submit">
<Button
style={{ background: "#009688", color: "#fff" }}
htmlType="submit"
loading={loading}
>
查询
</Button>
</Form.Item>
<Form.Item wrapperCol={{ span: 16 }}>
<Button onClick={handleReset}>重置</Button>
</Form.Item>
</div>
</Form>
- 引入使用
按照propsType按需传参即可
<Filter
pageName="introduceSubmited"
formItems={filterItems}
onSearch={fetchList}
loading={loading}
reSearchId={searchId}
/>
待迭代的功能
拖拽
这个只需要监听drag相关的api,获取到当前和拖拽目标后,交换数组中的位置进行rerender就可以了
展开/收起
也是比较简单的功能,比如我们可以为filterItems增加配置项,它大概就叫hide,值是一个boolean,当点击展开/收起按钮时,我们判断哪些身上hide属性为true哪些为false然后在进行筛选就行了
缓存
将当前状态下的选项状态通过localStorage设置到本地,下次进入时从这里获取就好了。但是我推荐web-localStorage-plus来管理本地缓存,原因嘛?因为是我写的,而且已经在项目中应用很久了
总结
本文分析并讲解了我是如何封装一个公共Filter查询组件来优化代码的,它应该是我这次做的优化中最简单的一个了,后边我们会分别分享关于Pop、Table和面包屑组件的实现:Pop是一个内部由N个表单组成的弹窗、Table则通过props支持了多级子Table的能力、面包屑是另一个vue项目中利用webpack loader实现的调用优化
如果本文对您有用,希望能得到您的❤
订阅专栏,每周更新2-3篇类型体操,每月2-4篇vue3源码解析,等你哟😎
github与好文
转载自:https://juejin.cn/post/7249020485471879228