「前端工作小记」关于业务组件的思考

简介: 用技术实现梦想,用梦想打开前端技术之门。分享我在日常开发中关于业务组件的思考。

前言

我们的节奏一般是双周迭代,大版本可能半个月到一个月,加上我偶尔会并行多个项目,去年,我差不多做了30个功能迭代,这里面不包括日常的临时修改需求或者线上bug维护等,平均一个月2.5个版本迭代。其中有半数以上是业务功能的开发。

对业务的思考

什么是业务功能开发?

我是这样理解的,以售卖商品流程为例,想要实现整个流程,需要有前端的销售页面、完整的购买流程流转页面、购买成功页,后台的售卖商品管理页、订单管理页等。业务功能开发,需要开发者了解要做什么以及怎么做。如果开发者不熟悉业务,可能会出现用户想买A产品,结果付款之后发现自己买成了B产品的情况。

业务功能干就完了?

大部分时候,我们接到的业务需求是在原来的功能上优化或者增强,这个时候可能不需要开发者花太多的时间就能完成。比如我近期的一个需求,拆了十几个小的修改点,基本都是在原来的基础上进行功能增强,比如加个按钮按照某个规则进行列表页的筛选,再比如将原来添加表单中的某些项单独拿出来,放到一个新的表单里面进行维护。这些需求并不难实现,如果我没有做任何思考,只是将功能实现,那么我的开发能力可能会停滞不前,且我的思维模式会定式。

无论是B端业务还是C端业务,技术都需要更好的服务于产品的使用者,即我们的用户。业务与技术开发密切相关,纯功能开发已无法满足日益增长和增强的功能需求。

正如我PPT里面所总结的,我在思考开发如何“赋能”业务,首先想到的是业务组件的建设。

image.jpeg


业务组件的理解

什么是业务组件

前端组件化开发,我们会将部分功能独立出来,将这部分功能的数据层、视图层、控制层全部封装在一个组件内,只暴露一些传参和方法,从而实现这部分功能的单独维护和重复使用。

业务组件是将某些和业务逻辑强相关的功能独立出来,封装在一个组件里,进行单独维护。和业务逻辑强相关意味着不会适用所有的需求开发,但是随着业务功能的壮大,我们还是能在星辰大海中,寻找某些闪光频率同步的星星,进行单独维护和管理的。


为什么封装业务组件

正如前面所讲的,业务功能在不断的壮大,我们项目中的代码会越来越多,代码逻辑也变得复杂。我们之所以要封装业务组件是因为:

  • 可以将复杂功能拆解,便于后续的快速迭代;
  • 解决跨项目复用的问题,减少重复代码和重复开发;
  • 统一代码质量,可以在快速开发的同时保证代码质量。


如何界定某个业务功能能否封装为业务组件

界定主要看以下几点:

  • 具有相似的页面展示和交互;
  • 使用类似的数据;
  • 一致的处理流程;
  • 相似的业务目标。


业务组件和基础组件的区别

我举例说明会看得更加明白。比如我们将列表组件进行了封装,无论怎样的业务需求,如果需要新增一个带分页的列表页面,基本都可以使用列表组件进行快速开发,这个无关具体的业务功能,可以视为一个基础组件;但是如果是一个备注功能,只有一部分业务功能需要,而这些业务功能又属于不同的页面,比如订单管理列表页、产品管理列表页,页面交互和接口的是相同的,可能接口入参不一样,这个备注弹窗就可以封装为一个业务组件。


业务组件的实现

我们的项目是基于React+Antd开发的,所以UI组件直接使用Antd提供的,写法主要是JSX+Hooks的语法。

项目结构

我们的项目基本是如下结构,包括接口、业务组件、基础组件、常量、css模块、业务模块、工具类等几个部分,这样的结构方便开发和维护。基于的业务的理解和思考,我们会根据实际情况封装一些业务组件和业务工具类等。也是因为做了这些工作,使得我能够在大多数的迭代开发中,节约不少的开发时间,且开发质量是很高的(提测阶段和线上bug明显减少了很多)。

image.jpeg


资料编辑/查看组件的实现

UI

资料查看

image.jpeg


资料上传

image.jpeg


组件封装

根据上面的UI不难看出资料查看和资料上传两个弹窗的主要区别是弹窗标题、弹窗内容、是否可操作、弹窗底部的按钮。所以我做了以下处理:

  • 组件通信:父子通信,父组件向子组件通过props传参,主要参数有visible-弹窗是否展示的布尔值、data-操作数据、onCancel-取消操作的回调函数,使用PropTypes提供的验证器进行参数的类型验证;子组件向父组件通信通过回调函数-onCancel;
  • 区分弹窗类型:设置了modalType变量区分弹窗类型,枚举值为:view:资料查看,edit:资料上传;
  • 区分弹窗内容、操作、底部按钮等差异:设置了商品对象:productObj,用于区分差异内容、操作、底部按钮;
  • 上传组件:我们将上传组件进行了二次封装,可以配合antd自带的From组件一起使用。
/** * @description 商品业务-资料编辑/查看 */importReact, { useRef, useState, useEffect } from'react';
importPropTypesfrom'prop-types';
import { Form, Modal, Input, Button, Space } from'antd';
import { ExclamationCircleFilled } from'@ant-design/icons';
import { Upload } from'@/components';
constProductMaterial= ({ visible, data, onCancel }) => {
constformRef=useRef({});
constlayout= {
labelCol: { span: 4 },
wrapperCol: { span: 20 },
  };
const [confirmLoading, setConfirmLoading] =useState(false);
const [productItem, setProductItem] =useState({});
/**   * 操作-关闭弹框   * @param {string} type 要关闭的弹框key值   * @param {boolean} refresh 弹窗关闭后是否刷新列表   * @return {void} 无   */consthandleCancel=refresh=> {
setConfirmLoading(false);
setProductItem({});
onCancel&&onCancel(refresh);
  };
/**   * 操作-确定按钮   * @param {void} 无   * @return {void} 无   */consthandleOk= () => {
formRef.current.submit();
  };
/** @name 商品对象  */constproductObj= {
edit: {
modalTitle: '资料上传', // 弹窗展示标题productLabel: '详情文件', // 详情项label值endorseLabel: '批注文件', // 批注项label值footer: (
<><ButtononClick={() =>handleCancel(false)}>取消</Button><Buttontype="primary"onClick={handleOk}>确定</Button></>      ), // 底部按钮组    },
view: {
modalTitle: '资料查看',
productLabel: '详情查看',
endorseLabel: '批注查看',
footer: <ButtononClick={() =>handleCancel(false)}>关闭</Button>,    },
  };
useEffect(() => {
if (data.modalType) {
letproductItemInit=productObj[data.modalType];
productItemInit.editFlag=data.modalType==='edit'?false : true; // 是否可以编辑的布尔值setProductItem(productItemInit);
    }
  }, [visible]);
/**   * 操作-上传   * @param {string} type 上传图片类型   * @return {void} 无   */constuploadCallback=type=> {
returnurl=> {
formRef.current.setFieldsValue({
        [type]: url,
      });
    };
  };
/**   * 操作-提交   * @param {Object} value 表单数据对象   * @return {void} 无   */consthandleSubmit=value=> {
// 请求接口提交表单数据,请求成功之后进行结果回调到父组件onCancel&&onCancel();
  };
return (
<Modaltitle={productItem.modalTitle} width={800} visible={visible} confirmLoading={confirmLoading} footer={productItem.footer} onCancel={() =>handleCancel(false)}><Form {...layout} labelAlign="left"onFinish={handleSubmit} ref={formRef}>        {productItem.editFlag? (
<Spacestyle={{ marginBottom: '15px' }}><ExclamationCircleFilledstyle={{ color: '#d80000', fontSize: '16px' }} /> 上传文件的格式不限</Space>        ) : null}
<Form.Itemlabel={productItem.productLabel} name="productFileUrl"rules={[{ required: true, message: `请上传${productItem.productLabel}` }]}><Uploadcallback={uploadCallback('productFileUrl')} accept="*"limit={Infinity} disabled={productItem.disabled} isArray="true"/></Form.Item><Form.Itemlabel={productItem.endorseLabel} name="endorseFileUrl"><Uploadcallback={uploadCallback('endorseFileUrl')} accept="*"limit={Infinity} disabled={productItem.disabled} isArray="true"/></Form.Item><Form.Itemlabel="其他资料"name="otherFileUrl"><Uploadcallback={uploadCallback('otherFileUrl')} accept="*"limit={Infinity} disabled={productItem.disabled} isArray="true"/></Form.Item><Form.Itemname="remark"label="修改备注"><Input.TextAreamaxLength={1000} rows={3} placeholder="请填写修改备注"disabled={productItem.disabled} /></Form.Item></Form></Modal>  );
};
ProductMaterial.propTypes= {
visible: PropTypes.bool.isRequired, // 弹窗关闭控制变量 必传data: PropTypes.object.isRequired, // 组件入参 必传onCancel: PropTypes.func, // 弹窗关闭事件};
ProductMaterial.defaultProps= {
visible: false,
data: {},
};
exportdefaultProductMaterial;


组件引入

用法跟常见的基础组件基本一致

  • 在需要展示资料弹窗的页面引入ProductMaterial组件且将组件放到视图层;
  • 因为是列表操作,所以在表格数组中加入操作项,操作项里面放置操作按钮,我把查看和上传放一起了,正常需求中这两个按钮会放在表格不同的列里;
  • 添加操作函数,控制弹窗的打开和关闭以及上传之后的回调等。
/** * @description 商品管理-首页 */importReact, { useState, useRef } from'react';
import { Button } from'antd';
import { List } from'@/components';
import { PRODUCT_COLUMNS, PRODUCT_FIELDS } from'@/constants/product';
import { list } from'@/api/product';
// 业务组件引入import { ProductMaterial } from'@/bundleComponents';
constProductList= () => {
constlistRef=useRef();
letcolumns=_.cloneDeep(PRODUCT_COLUMNS);
const [visible, setVisible] =useState(false);
const [recordData, setRecordData] =useState(false);
/**   * 操作   * @param {boolean} visibleType 弹窗是否展示布尔值   * @param {Object} data 数据对象   * @param {boolean} refresh 列表是否刷新布尔值   * @return {void} 无   */constoperate= (visibleType, data= {}, refresh) => {
setVisible(visibleType);
setRecordData(data);
// =>true: 刷新列表if (refresh) {
// 刷新列表    }
  };
columns=columns.concat([
    {
title: '操作',
width: 200,
fixed: 'right',
// eslint-disable-next-linerender: (text, record) => (
<>          {/* 查看操作 */}
<ButtononClick={() => {
operate(true, { ...record, modalType: 'view' });
            }}
>资料查看</Button>          {/* 上传操作 */}
<Buttontype="primary"onClick={() => {
operate(true, { ...record, modalType: 'edit' });
            }}
style={{ marginLeft: '10px' }}
>资料上传</Button></>      ),
    },
  ]);
return (
<div><Listfields={PRODUCT_FIELDS} columns={columns} http={list} ref={listRef} />      {/* 业务组件使用 */}
<ProductMaterialvisible={visible} data={recordData} onCancel={refresh=>operate(false, {}, refresh)} /></div>  );
};
exportdefaultProductList;


总结

在大量且重复的业务需求中,寻找可以提炼、可以拆分的功能模块,即便是看似平常或者做习惯的功能,也能找到亮点,而这种亮点既能提升开发者的技术能力,又能提高开发质量,并且能帮助开发者跳出思维定式,可谓是一举多得。

遇到新的需求可以跳出一味的复制粘贴式的开发的思维定式,适当的思考如何设计自己的功能模块,进而让自己能更高质量和更高效率的完成迭代任务。

秋日里北风轻,今天是个好天气。

目录
相关文章
|
2月前
|
数据采集 前端开发 JavaScript
《花100块做个摸鱼小网站! 》第四篇—前端应用搭建和完成第一个热搜组件
本文档详细介绍了从零开始搭建一个包含前后端交互的热搜展示项目的全过程。通过本教程,读者不仅能学习到完整的项目开发流程,还能掌握爬虫技术和前后端交互的具体实践。适合有一定编程基础并对项目实战感兴趣的开发者参考。
68 1
|
2月前
|
JavaScript 前端开发 开发者
哇塞!Vue.js 与 Web Components 携手,掀起前端组件复用风暴,震撼你的开发世界!
【8月更文挑战第30天】这段内容介绍了Vue.js和Web Components在前端开发中的优势及二者结合的可能性。Vue.js提供高效简洁的组件化开发,单个组件包含模板、脚本和样式,方便构建复杂用户界面。Web Components作为新兴技术标准,利用自定义元素、Shadow DOM等技术创建封装性强的自定义HTML元素,实现跨框架复用。结合二者,不仅增强了Web Components的逻辑和交互功能,还实现了Vue.js组件在不同框架中的复用,提高了开发效率和可维护性。未来前端开发中,这种结合将大有可为。
104 0
|
27天前
|
SpringCloudAlibaba JavaScript 前端开发
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
分布式组件、nacos注册配置中心、openfegin远程调用、网关gateway、ES6脚本语言规范、vue、elementUI
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
|
2月前
|
Android开发 iOS开发 C#
Xamarin:用C#打造跨平台移动应用的终极利器——从零开始构建你的第一个iOS与Android通用App,体验前所未有的高效与便捷开发之旅
【8月更文挑战第31天】Xamarin 是一个强大的框架,允许开发者使用单一的 C# 代码库构建高性能的原生移动应用,支持 iOS、Android 和 Windows 平台。作为微软的一部分,Xamarin 充分利用了 .NET 框架的强大功能,提供了丰富的 API 和工具集,简化了跨平台移动应用开发。本文通过一个简单的示例应用介绍了如何使用 Xamarin.Forms 快速创建跨平台应用,包括设置开发环境、定义用户界面和实现按钮点击事件处理逻辑。这个示例展示了 Xamarin.Forms 的基本功能,帮助开发者提高开发效率并实现一致的用户体验。
92 0
|
2月前
|
前端开发 UED 开发者
React组件优化全攻略:深度解析让你的前端应用飞速运行的秘诀——从PureComponent到React.memo的彻底性能比较
【8月更文挑战第31天】在构建现代Web应用时,性能是提升用户体验的关键因素。React作为主流前端库,其组件优化尤为重要。本文深入探讨了React组件优化策略,包括使用`PureComponent`、`React.memo`及避免不必要的渲染等方法,帮助开发者显著提升应用性能。通过实践案例对比优化前后效果,不仅提高了页面渲染速度,还增强了用户体验。优化React组件是每个开发者必须关注的重点。
54 0
|
2月前
|
JavaScript 前端开发
揭秘Vue.js组件魔法:如何轻松驾驭前端代码,让维护变得轻而易举?
【8月更文挑战第30天】本文探讨了如何利用Vue.js的组件化开发提升前端代码的可维护性。组件化开发将复杂页面拆分为独立、可复用的组件,提高开发效率和代码可维护性。Vue.js支持全局及局部组件注册,并提供了多种组件间通信方式如props、事件等。通过示例展示了组件定义、数据传递及复用组合的方法,强调了组件化开发在实际项目中的重要性。
25 0
|
2月前
|
资源调度 JavaScript 前端开发
Vue3+TypeScript前端项目新纪元:揭秘高效事件总线Mitt,轻松驾驭组件间通信的艺术!
【8月更文挑战第3天】Vue3结合TypeScript强化了类型安全与组件化开发。面对大型应用中复杂的组件通信挑战,可通过引入轻量级事件发射器Mitt实现事件总线模式。Mitt易于集成,通过简单几步即可完成安装与配置:安装Mitt、创建事件总线实例、并在组件中使用`emit`与`on`方法发送及监听事件。此外,利用TypeScript的强大类型系统确保事件处理器正确无误。这种方式有助于保持代码整洁、解耦组件,同时提高应用的可维护性和扩展性。不过,在大规模项目中需谨慎使用,以防事件流过于复杂难以管理。
80 1
|
2月前
|
前端开发 JavaScript 机器人
中后台前端开发问题之动态标注组件渲染到界面上如何解决
中后台前端开发问题之动态标注组件渲染到界面上如何解决
27 0
|
2月前
|
Web App开发 前端开发 JavaScript
React——前端开发中模块与组件【四】
React——前端开发中模块与组件【四】
29 0
|
3月前
|
存储 前端开发 JavaScript
前端框架与库 - React基础:组件、Props、State
【7月更文挑战第12天】React是JavaScript库,专注UI构建,基于组件化。组件是UI模块,可函数式或类定义。Props是组件间安全传递数据的只读参数,用defaultProps和propTypes保证正确性。State则是组件内部可变数据,用于驱动更新。使用setState()确保正确变更和渲染。了解并妥善处理这些概念是高效React开发的基础。
64 7