JS前端组件设计以业务为导向实践思考
引言
本文重点探讨前端组件设计。我相信好的组件设计不仅需要考虑技术实现,同时也需要考虑用户体验、可扩展性和易用性等多个方面。因此,我会重点强调这些方面的实现方法,从而更好地为产品的实际需求服务。我不打算在这里重复一些大家都耳熟能详的前端组件设计的原则和技巧。设计永远是为需求服务的。
下面,让我们进入正题。
一个组件设计的例子
考虑一个业务场景。一个表单组件,用来采集用户的基本信息,每个甲方爸爸都有自己的定制需求。比如样式、比如要展示的控件种类。分析表单控件类型,假设分别可能有文本输入框、单选框、多选框、下拉框等等。
我们很容易就能想到用动态表单来实现,支持自定义控件类型,支持自定义表单元素的样式和行为。api设计为了易于扩展,泛型显然更具可重用性。
一个基本的表单类型像这样:
type FieldProps<T> = { label: string; name: string; value: T; onChange: (name: string, value: T) => void; };
创建一个接口,表示 Field 组件可以渲染的不同类型的表单元素:
interface FieldRenderer<T> { (props: FieldProps<T>): JSX.Element; }
接着,我们可以创建一个泛型的 Field 组件,根据传入的泛型类型 T,来确定 Field 组件要渲染的表单元素类型以及 props 的类型。
function Field<T>({ label, name, value, onChange, render }: FieldProps<T> & { render: FieldRenderer<T> }) { return ( <div className="field"> <label htmlFor={name}>{label}</label> {render({ label, name, value, onChange })} </div> ); }
创建一些不同的 Field 组件渲染器,比如 TextInput、SelectInput、CheckboxInput。
const TextInput: FieldRenderer<string> = ({ label, name, value, onChange }) => ( <input type="text" id={name} name={name} value={value} onChange={(e) => onChange(name, e.target.value)} /> ); const SelectInput: FieldRenderer<string> = ({ label, name, value, options, onChange }) => ( <select id={name} name={name} value={value} onChange={(e) => onChange(name, e.target.value)}> {options.map(option=><option value={option.value}>{option.label}</option>)} </select> );
现在,我们可以在使用 Field 组件的时候,传入不同的泛型类型,来渲染不同的表单元素了。
const DynamicForm = ({ fields, onSubmit }: { fields: FormField[], onSubmit: ()=>void }) => { //这里做一些映射之类的。。 return ( <form onSubmit={onSubmit}> {fields.map(field=><Field {...field} render={field.customComponent} />)} </form> ) }
以上,我们基本完成一个高度抽象化的组件设计。
现在,让我们的目光从这个细节上挪开,切换到一个宏观的视角上,重新审视项目整体架构的组件设计。一个前端架构通常会有其系统规范,比如统一的命名规范、代码风格,合理的文件组织结构,前端开发的基础设施,性能优化方案,依赖管理等。那么,我们如何在这个规范的框架下设计组件呢?
模块化设计原则
从整体角度规划方案,我们可以对项目进行分层和模块化的设计,实现不同模块之间的解耦合。
分层设计是指将整个系统分成分层模块,每一层模块都有自己的职责和功能。前端的分层设计主要涉及以下几层:展示层、控制层、逻辑层、服务层。模块化设计是指将整个系统分成小的模块,每个模块都有自己的功能,不同模块之间通过明确的接口进行通信和数据交换。在前端项目中,往往可以将不同的功能分配到不同的模块中,甚至可以将某些通用的功能写成独立的模块进行引入。
在实际业务中,我们通常需要将分层设计与模块化设计相结合。在不同的层次上实现代码结构的划分和内部逻辑的编写,不同的功能分配到不同的模块,通用的功能写成独立的模块引入,将代码封装成可复用的单元。
业务组件的设计因素
业务组件是在实现业务过程中抽象出来的组件,作用是在应用中复用业务逻辑。我们应该进行怎样的抽象?简单的功能如果抽象成组件,是否是一种过度设计?我们尝试从以下几个角度来思考这些问题。
1. 状态与接口
在设计接口时,我们都知道,接口应该简单清晰、易于扩展。比如一个loading flag, 我们通常会用布尔值来切换loading状态,分别展示不同的UI界面。
比如一个发送验证码的按钮,我们可能用isEnd
就能满足展示不同按钮文字的需求。但如果,我们分别需要在点击按钮前、点击后的倒计时阶段、倒计时进入到指定的时刻、倒计时结束后执行不同的逻辑。那么,我们就需要考虑将这个接口设计成字符串,以便于扩展。
同理,一些使用loading布尔值的场景,是否可以考虑设计为字符串,以便满足更加个性化的需求?站在用户的角度,你是否已经厌倦了在等待一份大体积的数据时看着一个动画圈圈在转动?
现在,我们来考虑组件的使用场景。
这个组件是否与业务逻辑绑定?比如一个登录功能,可以是弹窗、也可以是单独的页面,它往往带有以下功能:用户名和密码的前端校验规则、对后端响应的处理、完成登录后的逻辑处理。像这样的组件,就不需要抽象,因为它难以通过修改参数就直接在其它系统中使用。
这个组件的功能有可能被重用吗?如果是,我们如何做预先的接口设计?比如一个表格组件,通常包含以下状态:要展示的数据、对数据的排序规则、数据过滤规则、用户选择器。我们初期可能据此做了4个接口:数据、排序、过滤、选择器。随着业务的扩展,我们可以预见后期的数据量开始加大,我们可能考虑增加一个分页接口。但是分页接口是否真的需要被集成在这个表格组件中?这是一个开放性的问题,相信不同的CRUD专家会有不同的解决方案。
2. 调用方式
组件的调用方式有多种,比如在模版文件中引入组件标签直接调用,或通过函数调用(常见的message/loading类组件),或者通过接口调用(Ant Design的DatePicker)等。并没有一套通用的标准来指定某种类型的组件的调用方式,总体还是取决于项目的需求和场景。
3. 测试
假设一个组件需要访问api,这很常见。我们应该如何设计以便于在测试组件时隔离组件的功能?
import APIService from './APIService'; function MyComponent({ apiService }) { const fetchData = () => { const data = apiService.get('/data'); // 处理数据并返回结果 } return <>{/* 渲染组件的内容 */}</> } // 渲染组件时,可以将 APIService 实例作为 props 传递 const apiService = new APIService(); ReactDOM.render(<MyComponent apiService={apiService} />, document.getElementById('root'));
在组件内部,我们定义了一个 fetchData
函数,它可以在需要的时候调用 apiService.get()
来获取数据。此时,我们可以轻松地模拟 apiService
,以进行单元测试,而不会对 MyComponent
的实现产生任何影响。
大多数时候,一个单一职责的组件的功能测试,是要比复合组件更容易的。使用标准和通用的API和数据格式,并将组件的功能和状态限制在组件内部,确保组件可以独立地进行测试。此外,我们还要考虑边界测试,比如组件接收到无效的或非预期的参数该如何处理?
当然,组件不是颗粒度越细越好。是否遵循单一职责原则,不应该以功能点的数量,而是以功能和目标来衡量。
4. 文档
编写一份前端组件设计文档,明确记录项目的设计标准和项目迭代过程中的变更,创建标准的组件库以便于多人协作时的组件复用。尤其在一个多人协作的项目里,文档能够提供统一的开发规范,使得不同的开发人员在编写不同的代码时能保持一致的开发习惯。
分析业务逻辑
我们已经有了设计的原则和需要考虑的因素,下面我们开始分析业务逻辑。
通常我们会有原型图展示各个业务操作的流程和业务环节的逻辑关系,在设计组件时我们需要考虑如何支持这些流程和操作。比如根据业务逻辑,将整个项目分解为多个独立的组件,划分组件的接口和参数,指定其数据类型等。
此外,我们还要分析各种数据的处理和存储方式。这里我们只讨论前端的管理方式。比如一个给定的系统,我们通常会有用户数据、产品数据、订单数据等。我们可能会考虑使用状态管理工具、Context API、组件间的通信、本地存储等方式来管理这些数据。不同的场景需要采用不同的数据管理方法。
最后,我们要分析用户交互的影响,考虑控制用户的交互范围。比如表单校验、提高反馈信息和错误处理。
总结
当前端组件设计融合艺术与技术,创造出优美、流畅的用户体验时,似乎所有的麻烦都随之而消失。通过组件化设计、数据管理、用户交互分析等手段,我们可以打造出易维护、易扩展的代码,为用户提供舒适愉悦的体验。在这个信息化的时代,前端组件设计越来越重要,只有注重细节,不断优化性能和加强安全防范,我们才能成为一名真正的CRUD艺术家。
以上就是JS前端组件设计以业务为导向实践思考的详细内容,更多关于JS业务导向前端组件设计的资料请关注脚本之家其它相关文章!
相关文章
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
有时候我们更新的内容,有很多的大图片,就会导致页面变形或看不到全图。一般情况我们用css的max-width控制,但有些浏览器不支持,我们也可以用js做个补充2014-02-02
最新评论