靠谱的React组件设计准则

简介: 为开发者设计

编写React组件时, 我经常问自己几个问题 :

  • 如何减少使用者在使用过程中不必要的思考顾虑 , 方便快捷的达到使用者的需求和目的?
  • 如何降低与其他组件组合带来的排斥度?
  • 如何让其他开发者积极参与进来, 进行可持续扩展维护?

组件

简介

简单来说就是把页面想象成乐高玩具,需要不同零件组装,然后将各个部分拼到一起

落实到实际应用开发中像这样

一个组件一个目录, 组件可组合相互依赖 , 组件所需的各种资源都在这个目录下就近维护


组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

设计原则

我们需要具有组件化设计思维,它是一种【整理术】帮助我们高效开发整合:


  1. 单一职责

单一职责可以保证组件是最细的粒度,且有利于复用。但太细的粒度有时又会造成组件的碎片化。

因此单一职责组件要建立在可复用的基础上,对于不可复用的单一职责组件,我们仅仅作为独立组件的内部组件即可。

  1. 通用性

组件开发要服务于业务,为了更好的复用,又要从业务中抽离。

  1. 封装

良好的组件封装应该隐藏内部细节和实现意义,并通过props来控制行为和输出。

减少访问全局变量:因为它们打破了封装,创造了不可预测的行为,并且使测试变得困难。可以将全局变量作为组件的props,而不是直接引用。

  1. 组合

具有多个功能的组件,应该转换为多个小组件。

单一责任原则描述了如何将需求拆分为组件,封装描述了如何组织这些组件,组合描述了如何将整个系统粘合在一起。

  1. 可测试

测试不仅仅是自动检测错误,更是检测组件的逻辑。
如果一个组件不易于测试,很大可能是你的组件设计存在问题。

  1. 富有意义

开发人员大部分时间都在阅读和理解代码,而不是实际编写代码。
有意义的函数、变量命名、注释可以让代码具有良好的可读性。


职能分类



组件命名

个人觉得好的命名规范,  能成倍增加使用者的好感,  相反,  晦涩的命名总是让人望而却步


帕斯卡命名法

组件名是由一个或多个帕斯卡单词(主要是名词)串联起来的,比如:<DatePicker><GridItem><SearchList>

语义专业化

有意义的名称足以使代码可读, 并且减少了很多不必要的注释

// 一些好的命名,专业易懂,看到就知道用处// 视频展示面板<VideoPannel>// 视频上方覆盖层<VideoLayer>// 实现内容可缩放的表格  <ResizeTable>// 一些不好的命名// 看名字都不知道是什么 大概能猜出来是Tabs、Button、Icon<Zabs><Zutton><Zcon>// 本身就是通用表单操作栏组件, 改为 FooterToolBar 会更合理<CommonFormFooterToolBar>// 名称太过冗长,可读性差, 使用者望而却步  <TableTextoverflowNofixedNoscroll>// 通用组件应避免拥有耦合业务过深的组件名或属性名// 这里是学员选择器,如果要提取该组件作为通用组件,可能业务上会出现如教师、工程师其他职业的人员,改为PersonSeletor更合理<StudentSeletor>// bad<AutoTablefetch={}
isRefresh={}
leftBtn={}
rightBtn={}
/>// good<AutoTablerequest={}
refresh={}
extra={}
/>


封装组合

封装也就是 松耦合, 是我们设计应用结构和组件之间关系的目标。

组合是一种通过将各组件联合在一起以创建更大组件的方式。组合是 React 的核心。


单一责任

组合的一个重要方面在于能够从特定的小组件组成复杂组件的能力。这种分而治之的方式帮助了被组合而成的复杂组件也能符合 SRP 原则。

下面举两个开发过程中的例子:

权限按钮组件目录


  • AuthButton:  直接对于全局的权限按钮进行状态控制
  • Authorized: 利用render props特性抽象权限点的表现形式, 可能是菜单、链接或具体内容模块
  • useAuthorized: 抽象实现权限业务逻辑


重型Table组件目录


  • AutoTable: 辅助代码分离、做为内部通信中转站
  • CommonTable:  封装全局定制的表格样式、基本渲染内容和形式
  • useTable: 抽象实现表格的自动请求、搜索、分页、刷新、重置、筛选、排序等业务逻辑
  • SearchForm: Json配置化管理表格搜索表单, 可作为独立组件的内部组件, 也可以抽离为通用搜索表单组件


可复用

组合有可复用的点,使用组合的组件可以重用公共逻辑。

例如,组件<Comp1><Comp2>有一些公共代码:

constinstance1= (
<Comp1>/* Common code... *//* Specific to Comp1 code... */</Comp1>);
constinstance2= (
<Comp2>/* Common code... *//* Specific to Comp2 code... */</Comp2>);

将共同代码封装抽离到一个新组件中, 然后进行组合

constinstance1= (
<Comp1><Common/><Piece1/></Comp1>);
constinstance2= (
<Comp2><Common/><Piece2/></Comp2>);

灵活高效

一个组合式的组件通过给子组件传递 props的方式,来控制其子组件。这就带来了灵活性的好处。

例如,有一个组件,它需要根据用户的设备显示信息,使用组合可以灵活地实现这个需求:

functionByDevice({ children: { mobile, other } }) {
returnUtils.isMobile() ?mobile : other;
}
<ByDevice>{{
mobile: <div>Mobiledetected!</div>,other: <div>Notamobiledevice</div>}}</ByDevice>

ByDevice 组合组件,对于移动设备,显示: Mobile detected!; 对于非移动设备,显示 Not a mobile device"。

常见抽象逻辑技巧

HOC

高阶组件通过包裹(wrapped)被传入的React组件,经过一系列处理,最终返回一个相对增强(enhanced)的React组件(注意:高阶组件的本质是函数,不是组件,  属于设计模式的装饰器Decorator模式)

const HOC = Component => EnhancedComponent
使用方式:

1.属性代理,代理传递给被包装组件的 props, 对 props 进行操作

mportReact, { Component } from'react';
exportdefaultWrappedComponent=> {
returnclassextendsComponent {
moveRef=React.createRef();
dragDown=e=> {
...    };
render() {
return (
<divref={this.moveRef}
onMouseDown={e=>this.dragDown(e)}
style={{
position: 'fixed',
left: 0,
top: 0,
cursor: 'move',
zIndex: 999,
          }}
><WrappedComponent {...this.props} /></div>      );
    }
  };
};
@withDragclassTeamCardextendsReact.PureComponent {}
// 或者exportdefaultwithDrag(TeamCard);

2.反向继承(高阶组件继承被包装的组件)

constwithWebSocket=WrappedComponent=> {
returnclassextendsWrappedComponent {
constructor(props) {
super(props);
    }
componentDidMount() {
this.connection();
    }
componentWillUnmount() {
this.disconnect();
    }
// 建立连接connection() {}
// 断开连接disconnect() {    }
render() {
returnsuper.render();
    }
  };
};
@withWebSocketclassCommandCenterPageextendsReact.Component {}

react-router的withRouter(),redux的connect()都是HOC组件


render props

个人觉得是很有意思的一种编码方式, 但是比较冷门 ,通常用于父子组件解藕的同时,又保持着通信

constList=props=><>    {props.children(props)}
</><List>    {() =><Card/>}
</List><List>    {() =><Pannel/>}
</List>

自定义hooks 取代高阶组件

react函数式编程的大改革

// 抽象保留状态的前一个值的逻辑constusePrevious=value=> {
constref=useRef();
useEffect(() => {
ref.current=value;
  }, [value]);
returnref.current;
};

react-redux的useSeletor()useDispatch()就是抽象状态管理的hooks钩子


设计不当导致的一些常见问题 :

环形依赖

image.png

组件间耦合度高,集成测试难 一处修改,处处影响,交付周期长 因为组件之间存在循环依赖,变成了“先有鸡还是先有蛋”的问题

消除环形依赖 , 创建一个共同依赖的新组件



复用第三方库

某个工作日,你刚刚收到了为应用增加新特性的任务,在撩起袖子狂敲代码之前,先稍等几分钟


你要做的工作在很大概率上已经被解决了。由于 React 非常流行以及其非常棒的开源社区,先搜索一下是否有已存在的解决方案是明智之举


个人是反对重复造轮子的, 一些成熟的第三方库, 可以更高效和高质量的完成部分开发工作 , 提高工作效率, 站在巨人的肩膀上🤤


如:  React Hooks 库ahooks、UI组件库 Ant Design一些精选React组件库、业务开发组件库 Orca-team


最近随着项目业务需求疯狂增长,  之前封装的通用组件迎来了很多改变, 由于项目前端架构采用了微前端方案,导致了很多子应用都需要引用重复的通用组件, 这个时候我是通过将组件发布到npm上进行管理的, 但是面临几个问题:


  1. 我开发的组件和发布流程通常是我一个人来维护的
  2. 没有类似于antd组件库的组件api介绍、以及丰富的demo展示


这样一来会让增加了其他开发者的开发成本, 甚至导致对于组件理解偏差导致错误使用造成功能问题, 二来也降低了大家的积极性,可能有的开发同学就自行去重写一套了, 不利于协作


找了一下 , 发现了一个干货 Dumi  , 可以快速实现组件库+文档的工具 , 真香~

结尾

最后总结一句话 , 为开发者而设计

组件设计与产品设计思想如出一辙, 致力于提升开发者或用户的使用体验,  因此,要为你未来的用户设计,在一个月内为自己设计,为那些在你离开后必须维护你代码的可怜兄 dei 设计,为开发者设计!

相关资料摘要

react是一门哲学

相关文章
|
4月前
|
设计模式 前端开发 数据可视化
【第4期】一文了解React UI 组件库
【第4期】一文了解React UI 组件库
107 0
|
4月前
|
存储 前端开发 JavaScript
【第34期】一文学会React组件传值
【第34期】一文学会React组件传值
32 0
|
4月前
|
前端开发
【第31期】一文学会用React Hooks组件编写组件
【第31期】一文学会用React Hooks组件编写组件
35 0
|
4月前
|
存储 前端开发 JavaScript
【第29期】一文学会用React类组件编写组件
【第29期】一文学会用React类组件编写组件
31 0
|
4月前
|
前端开发 开发者
【第26期】一文读懂React组件编写方式
【第26期】一文读懂React组件编写方式
28 0
|
4月前
|
资源调度 前端开发 JavaScript
React 的antd-mobile 组件库,嵌套路由
React 的antd-mobile 组件库,嵌套路由
42 0
|
3月前
|
存储 前端开发 中间件
React组件间的通信
React组件间的通信
17 1
|
3月前
|
前端开发 应用服务中间件 数据库
react服务端组件
react服务端组件
21 0
|
3月前
|
前端开发 JavaScript
快速上手React:从概述到组件与事件处理
快速上手React:从概述到组件与事件处理
|
4月前
|
前端开发 JavaScript API
React组件生命周期
React组件生命周期
76 1