靠谱的React组件设计准则

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

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

组件

简介

简单来说就是把页面想象成乐高玩具,需要不同零件组装,然后将各个部分拼到一起 落实到实际应用开发中像这样

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

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

设计原则

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

  1. 单一职责

单一职责可以保证组件是最细的粒度,且有利于复用。但太细的粒度有时又会造成组件的碎片化。 因此单一职责组件要建立在可复用的基础上,对于不可复用的单一职责组件,我们仅仅作为独立组件的内部组件即可。

  1. 通用性

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

  1. 封装

良好的组件封装应该隐藏内部细节和实现意义,并通过props来控制行为和输出。 减少访问全局变量:因为它们打破了封装,创造了不可预测的行为,并且使测试变得困难。可以将全局变量作为组件的props,而不是直接引用。

  1. 组合

具有多个功能的组件,应该转换为多个小组件。 单一责任原则描述了如何将需求拆分为组件,封装描述了如何组织这些组件,组合描述了如何将整个系统粘合在一起。

  1. 可测试

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

  1. 富有意义

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

职能分类

组件命名

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

帕斯卡命名法 组件名是由一个或多个帕斯卡单词(主要是名词)串联起来的,比如:<DatePicker><GridItem><SearchList>语义专业化 有意义的名称足以使代码可读, 并且减少了很多不必要的注释

tsx 复制代码
// 一些好的命名,专业易懂,看到就知道用处

// 视频展示面板
<VideoPannel>  
  
// 视频上方覆盖层
<VideoLayer> 
  
// 实现内容可缩放的表格  
<ResizeTable> 


// 一些不好的命名

// 看名字都不知道是什么 大概能猜出来是Tabs、Button、Icon
<Zabs> 
<Zutton>
<Zcon>  
  
// 本身就是通用表单操作栏组件, 改为 FooterToolBar 会更合理
<CommonFormFooterToolBar>  
  
// 名称太过冗长,可读性差, 使用者望而却步  
<TableTextoverflowNofixedNoscroll> 
  
// 通用组件应避免拥有耦合业务过深的组件名或属性名

// 这里是学员选择器,如果要提取该组件作为通用组件,可能业务上会出现如教师、工程师其他职业的人员,改为PersonSeletor更合理
 <StudentSeletor> 

// bad
 <AutoTable
   fetch={}
   isRefresh={}
   leftBtn={}
   rightBtn={}
 />
     
// good
<AutoTable
   request={}
	 refresh={}
   extra={}
/>

封装组合

封装也就是 松耦合, 是我们设计应用结构和组件之间关系的目标。 组合是一种通过将各组件联合在一起以创建更大组件的方式。组合是 React 的核心。

单一责任

组合的一个重要方面在于能够从特定的小组件组成复杂组件的能力。这种分而治之的方式帮助了被组合而成的复杂组件也能符合 SRP 原则。 下面举两个开发过程中的例子:

权限按钮组件目录:

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

重型Table组件目录:

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

可复用

组合有可复用的点,使用组合的组件可以重用公共逻辑。 例如,组件 <Comp1><Comp2> 有一些公共代码:

tsx 复制代码
const instance1 = (
    <Comp1>
    /* Common code... */
    /* Specific to Comp1 code... */
    </Composed1>
);
const instance2 = (
    <Comp2>
    /* Common code... */
    /* Specific to Comp2 code... */
    </Comp2>
);

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

tsx 复制代码
const instance1 = (
    <Comp1>
       <Common />
       <Piece1 />
    </Composed1>
);
const instance2 = (
    <Comp2>
        <Common />
        <Piece2 />
    </Comp2>
);

灵活高效

一个组合式的组件通过给子组件传递 props的方式,来控制其子组件。这就带来了灵活性的好处。 例如,有一个组件,它需要根据用户的设备显示信息,使用组合可以灵活地实现这个需求:

tsx 复制代码
function ByDevice({ children: { mobile, other } }) {
    return Utils.isMobile() ? mobile : other;
}

<ByDevice>{{
    mobile: <div>Mobile detected!</div>,
    other: <div>Not a mobile device</div>
}}</ByDevice>

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

常见抽象逻辑技巧

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

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

tsx 复制代码
import React, { Component } from 'react';

export default WrappedComponent => {
  return class extends Component {
    moveRef = React.createRef();

    dragDown = e => {
     ...
    };

    render() {
      return (
        <div
          ref={this.moveRef}
          onMouseDown={e => this.dragDown(e)}
          style={{
            position: 'fixed',
            left: 0,
            top: 0,
            cursor: 'move',
            zIndex: 999,
          }}
        >
          <WrappedComponent {...this.props} />
        </div>
      );
    }
  };
};

@withDrag
class TeamCard extends React.PureComponent {}
// 或者
export default withDrag(TeamCard);

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

tsx 复制代码
const withWebSocket = WrappedComponent => {
  return class extends WrappedComponent {
    constructor(props) {
      super(props);
    }

    componentDidMount() {
      this.connection();
    }

    componentWillUnmount() {
      this.disconnect();
    }

    // 建立连接
    connection() {}

    // 断开连接
    disconnect() {    }

    render() {
      return super.render();
    }
  };
};

@withWebSocket
class CommandCenterPage extends React.Component {}

react-router的withRouter(),redux的connect()都是HOC组件 render props 个人觉得是很有意思的一种编码方式, 但是比较冷门 ,通常用于父子组件解藕的同时,又保持着通信

tsx 复制代码
const List = props => <>
    {props.children(props)}
</>

<List>
    {() => <Card />}
</List>

<List>
    {() => <Pannel />}
</List>

自定义hooks 取代高阶组件 react函数式编程的大改革

tsx 复制代码
// 抽象保留状态的前一个值的逻辑

const usePrevious = value => {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
};

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

**设计不当导致的一些常见问题 : ** 环形依赖 组件间耦合度高,集成测试难 一处修改,处处影响,交付周期长 因为组件之间存在循环依赖,变成了"先有鸡还是先有蛋"的问题 消除环形依赖 , 创建一个共同依赖的新组件

复用第三方库

某个工作日,你刚刚收到了为应用增加新特性的任务,在撩起袖子狂敲代码之前,先稍等几分钟。 你要做的工作在很大概率上已经被解决了。由于 React 非常流行以及其非常棒的开源社区,先搜索一下是否有已存在的解决方案是明智之举 个人是反对重复造轮子的, 一些成熟的第三方库, 可以更高效和高质量的完成部分开发工作 , 提高工作效率, 站在巨人的肩膀上🤤

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

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

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

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

结尾

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

相关资料摘要

react是一门哲学

相关推荐
柯南二号12 分钟前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
wyy729314 分钟前
v-html 富文本中图片使用element-ui image-viewer组件实现预览,并且阻止滚动条
前端·ui·html
前端郭德纲26 分钟前
ES6的Iterator 和 for...of 循环
前端·ecmascript·es6
王解32 分钟前
【模块化大作战】Webpack如何搞定CommonJS与ES6混战(3)
前端·webpack·es6
欲游山河十万里33 分钟前
(02)ES6教程——Map、Set、Reflect、Proxy、字符串、数值、对象、数组、函数
前端·ecmascript·es6
明辉光焱33 分钟前
【ES6】ES6中,如何实现桥接模式?
前端·javascript·es6·桥接模式
PyAIGCMaster1 小时前
python环境中,敏感数据的存储与读取问题解决方案
服务器·前端·python
baozhengw1 小时前
UniAPP快速入门教程(一)
前端·uni-app
nameofworld1 小时前
前端面试笔试(二)
前端·javascript·面试·学习方法·数组去重
帅比九日1 小时前
【HarmonyOS NEXT】实战——登录页面
前端·学习·华为·harmonyos