创建组件的10条准则

诚然,想要创建一个供多人使用的组件绝非易事,组件包含属性(props),如果这些属性要作为公开 API 的一部分,那就必须非常仔细地考虑组件应该接受哪些属性。

本文会简要介绍 API 设计中的一些最佳实践,同时还总结了开发优秀组件的10条准则。希望这些准则能够对你有所帮助。

什么是 API ?

API (Application Programing Interface,应用编程接口)是两段代码交互的地方。它是代码与世界沟通的桥梁,我们将这个桥梁称为接口。可以通过 API 进行数据与功能的交互。

后端和前端之间的接口是一个 API。 可以通过与这个 API 进行交互来访问一组数据和功能。

一个类和调用这个类的代码之间的接口同样也是一个 API。你可以调用类里的方法,来检索数据或触发封装在里面的功能。

同理,组件中要接收的 props 同样也是 API 。这是调用者与组件交互的方式,当你想要对外暴露什么的时候,会应用很多相同的规则和注意事项。

API 设计的一些最佳实践

那么,在设计 API 的时候,需要注意哪些规则和事项呢?我们在这方面做了一些研究,最后找到了许多非常优秀的资源。我们选取了其中的 2 篇:Josh Tauberer 的《What Makes a Good API ?》以及 Ron Kurir 的同名文章;并且也提出了4 个可遵循的最佳实践。

版本稳定

当创建一个 API 的时候,需要考虑的最重要的一件事是尽可能地保持 API 的稳定。这意味着需要最大限度地减少重大变化的数量。如果 API 真的有较大的变化,也请确保撰写了详细的升级指南,并尽可能提供一份代码模块,可以让用户自动完成升级过程。

如果正在发布 API,请确保遵循了语义版本规范,可以让用户轻松地决定所需的版本。

提供错误描述信息

每当调用 API 发生错误时,你需要尽可能地去解释发生了什么问题,以及如何去修复错误。在没有任何提醒或信息的情况下,直接抛出一个“错误使用”来羞辱调用者貌似不是一种良好的用户体验。

相反,撰写描述性错误信息可以帮助调用者来修改他们调用 API 的方式。

别让程序猿犯嘀咕

程序猿是脆弱的,而且你也不希望他们在使用 API 的时候来个 surprise,然后把人家吓出个好歹来。也就是说,API 应该尽可能直观。可以通过遵循最佳实践和现有的命名习惯来实现这一点。

另外还需要注意一点:保持代码风格的连贯。如果在布尔属性的名称前加上了 is 或者 has 作为前缀,接下来却又不这么做了,就会让人感到费解。

精简 API 结构

当我们在讨论做减法的时候,同样也包括减少 API 。功能多了固然很好,但是 API 的结构越简单,调用者的学习成本就越小。反过来讲,这会被认为是一个简单易用的 API 。

总有办法来控制 API 的大小,其中的一个办法是,从旧的 API 中重构出一个新的 API。

创建组件的 10 条准则

上面的 4 条黄金法则在编写 REST API 以及古老的 Pascal 程序时很管用,那应该如何转化才能适用于现代世界的 React 呢?

正如我们前面所提到的,组件有自己的 API。我们称其为 属性(props),这是我们提供给组件数据、回调函数以及其他功能的方式。我们应该以何种方式构建props对象,才能够不违反上述任何一条规范呢?我们同样应该以何种方式编写组件,才能让下一个开发者轻松地测试它们呢?

以下列出了创建组件时需要遵循的 10 条准则,并且希望你能发现它们是行之有效的。

写文档

如果没有文档来记录组件是如何使用的,好吧,虽然大多数开发者会随时查看你的代码,但这不能说是一种良好的用户体验。

写文档有很多工具,我们推荐以下 3 个:

  • Storybook
  • Styleguidist
  • Docz

前两个会在开发组件的时候提供一个演练场,第三个则会让你使用 MDX 编写更多自由格式的文档。

无论选择哪个,都请确保在文档中记录了 API 的用法 ,以及组件的用法和使用时机。 后者在共享组件库中尤为重要。

允许上下文语义

HTML 是一种通过语义化的方式来组织信息的语言。大多数组件是使用

标签来构建的。这在某种程度上是有道理的——因为通用组件不清楚它到底应该是一个 还是 或者是 ,尽管如此,但只用来构建也并不完美。

 

相反,我们建议允许组件接受一个 as 属性,将始终覆盖正在呈现的DOM 元素。下面是一个实现用例:

function Grid({ as: Element, ...props }) {return <Element className="grid" {...props} />
}
Grid.defaultProps = {as: 'div',
};

我们将 as 属性重命名为局部变量 Element,并在 JSX 中使用。当不需要更多语义化的 HTML 标签时,我们也提供了普通的默认值来传给组件。

当使用 组件的时候,你可以传入合适的标签:

function App() {return (<Grid as="main"><MoreContent /></Grid>);
}

请注意上面的代码在 React 组件中同样有效。下面是一个很好的例子,展示了如果想让一个组件呈现一个 React Router 。

<Button as={Link} to="/profile">Go to Profile
</Button>

避免布尔属性

布尔属性听起来不错,你不需要给它赋值就可以指定一个布尔属性,所以看起来非常优雅:

<Button large>BUY NOW!</Button>

尽管看起来很好,但是布尔属性却只允许有 2 个可选值:打开或者关闭,展示或者隐藏,1 或者 0。

每当你开始想为布尔属性引入些其他的东西的时候,比如尺寸、变体、颜色,或者其他可能除了二元选择之外的任何东西,就有些麻烦了。

<Button large small primary disabled secondary> WHAT AM I?? </Button>

换句话说,布尔属性常常不能随着需求的改变而进行扩展。相反,尝试使用类似字符串类型的可枚举类型来作为属性值,可以获得二元选择之外更多的选择。

<Button variant="primary" size="large"> I am primarily a large button </Button>

这并不代表布尔属性就完全没有一席之地了,其实布尔属性是有用的!上面列出的 disable 属性应当依旧是布尔类型——因为在“可用”与“不可用”的状态之间,不存在中间状态,所以这里用布尔类型是恰当的。

使用 props.children

React 中有几个特殊的属性,他们的处理方式与其他属性不太一样。其中一个就是key,用来在有序列表中追踪列表项的,另一个就是children。

在一个开始标签和结束标签之间的任何东西都被放置在props.children属性中,推荐尽量多使用这个属性。

原因是使用props.children属性比起使用content属性,或者其他只接受类似文本的简单值的属性来说,要简便的多。

<TableCell content="Some text" /> // vs <TableCell>Some text</TableCell>

使用 props.children还有几个好处。首先,它的写法和普通的 HTML 是一样的。第二,你可以向组件传递任何想要的东西,而不是向组件中添加 leftIcon 和 rightIcon 属性,把他们作为 props.children 的一部分传递给组件即可。

<TableCell> <ImportantIcon /> Some text </TableCell>

让父组件的钩子函数进入内部逻辑

有时,我们会创建一些内部逻辑复杂的组件,比如自动补全的下拉菜单或者可交互图表。

这种类型的组件通常会有冗长复杂的 API ,其中原因是,需要覆盖的功能以及需要支持的特殊用法,这两者的数量都会随着时间的推移而不断增加。

如果我们想提供一个简单且标准化的属性,来让调用者去控制或者覆盖组件的默认行为,我们应该怎么做呢?

Kent C. Dodds 为此写过一篇很棒的文章,在文中他将这个问题的解决方案称为:state reducers。请参阅这两篇文章:Post about the concept itself 和 How to implement it for React Hooks。

简单总结来说,这种通过传递 state reducer 函数到组件中的模式,允许调用者访问组件内部分派的所有操作。你可以修改 state ,或者触发内部事件。这是一种创建无需 prop 的高度自定义组件很好的方式。

function MyCustomDropdown(props) {const stateReducer = (state, action) => {if (action.type === Dropdown.actions.CLOSE) {buttonRef.current.focus();}};return (<><Dropdown stateReducer={stateReducer} {...props} /><Button ref={buttonRef}>Open</Button></>
}

顺便提一下,你当然可以创建更简单的方式来响应事件。在上面的例子中,在组件中提供一个 onClose 属性会产生更好的用户体验。

扩散剩余属性

每当创建一个新的组件时,请确保将剩余属性也扩散到有意义的元素上。

如果有某些属性仅需传递给子组件或子元素(而组件自身并不需要这个属性),那就不必添加到你的组件中,这么做可以让组件 API 更加稳定,即使当下一个开发者需要新事件监听器的时候,也无需发布新版本的组件。

function ToolTip({ isVisible, ...rest }) {return isVisible ? <span role="tooltip" {...rest} /> : null;
}

你的组件可以向底层组件或元素传递属性,比如 className 或者 ·onClick 的监听函数,一定要确保外部的调用者一样可以这样做。比如在 class 这种情况中,你可以使用 npm 上的 classname 包来方便地添加 class 属性(或者干脆直接用简单的 string 字符串)。

import classNames from 'classnames';
function ToolTip(props) {return (<span {...props} className={classNames('tooltip', props.tooltip)} />
}

在事件监听回调的情况下,可以用一个小工具函数将它们合并成单个函数。 例如:

function combine(...functions) {return (...args) =>functions.filter(func => typeof func === 'function').forEach(func => func(...args));
}

现在,我们创建了一个以函数数组为参数的函数,它返回一个新的回调函数,该回调函数会向各个函数传入相同的参数,并依次调用各个函数。

function ToolTip(props) {const [isVisible, setVisible] = React.useState(false);return (<span {...props}className={classNames('tooltip', props.className)}onMouseIn={combine(() => setVisible(true), props.onMouseIn)}onMouseOut={combine(() => setVisible(false), props.onMouseOut)}/>);
}

充分提供默认值

请确保为属性提供了充分的默认值,这样做可以最大限度地减少必传值的数量,而且也大大简化了代码实现。

以 onClick 处理函数为例,如果它不是必需的,就可以提供一个空函数来作为默认值。这样,你就可以在代码中随时调用它,就好像组件总是被提供了回调函数一样。

另一个例子是自定义输入。 除非明确提供,否则假设输入的字符串是空字符串。 这将使你确保始终处理字符串对象,而不是 undefined 或 null 。

不要重命名 HTML 属性

HTML 作为一门语言拥有自己的属性,它本身就是 HTML 元素的 API,为啥不继续使用这些 API?

正如前面所提到的,精简 API 数量并使其具有一定的直观性是改进组件API的两种很好的方法。所以与其创建自己的自定义标签属性,为什么不直接使用现成的原生标签 API 呢?

因此,不要重命名任何现有 HTML 属性。甚至没有用新的 API 替换现有的 API,你只是在上面添加了自己的 API。其他人仍然可以将原生标签与你自定义标签的属性一起传递,那么最终的值应该是什么呢?

另外,也请确保在组件中没有将 HTML 属性进行覆盖。一个很好的例子就是

元素的 type 属性。它的值可以是 submit (默认值)、button 和 reset。但是,许多开发者却倾向于将这个属性名用于表示按钮的可视类型(primary,cta 等等)。

通过改变这个属性的用途,你不得不添加其他属性来覆盖,设置实际的 type 属性值,这只会给调用者带来困惑。

指定属性的类型

没有什么文档比代码中的文档更好了,React 提供了 prop-types 包来声明组件 API 的类型,一定要用它。

你可以为所有的属性指定明确的类型,也可以规定属性是否必传,甚至可以使用 JSDoc 注释来做进一步改进。

如果忽略了必传属性,或者传递了无效值和意外值,运行时会在控制台打印警告。这样的开发体验很棒,并且可以从生产构建中剥离出来。

如果使用 TypeScript 或者 Flow 来编写 React 应用,就可以将此类API文档作为语言功能。这会带来更好的工具支持以及更棒的开发体验。

如果你自己没有使用类型化的 JavaScript,仍然应该考虑为那些使用类型化的 JavaScript 调用者提供类型定义。通过这种方式,他们能够更轻松地使用你的组件。

为开发者而设计

最后,需要遵循的最重要一条规则就是:保证 API 以及“组件体验”已经针对它的调用者进行了优化。

提升开发者体验的一个办法是在错误调用时提供详细的错误信息,以及在开发过程中的警告信息。

当编写错误和警告时,记得使用链接引用文档或提供简单的代码示例。这可以帮助开发者更快地发现错误并修改,提供更好的开发体验。

不必担心过于冗长的错误信息会占用太大空间,况且构建生产环境的时候也不会把这些信息打包进去。

React 本身就是一个非常优秀的类库,当你忘记使用 key 或者拼错了生命周期的名字等等,都会在控制台收到大量详细的错误警告信息。

因此,要为你未来的用户设计,在 5 周内为自己设计,为那些在你离开后必须维护你代码的可怜兄 dei 设计,为开发者设计!

总结

在经典的 API 设计中我们还可以学到很多优秀的东西,通过遵循本文中提到的提示和准则,你应该可以创建出易于使用、便于维护、使用直观,以及出现问题时可以进行快速修复的组件。 你还有哪些关于创建组件的点子?

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/811031.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

性能测试 之 接口性能优化(索引相关)

导致接口性能出现问题的原因非常多&#xff0c;千奇百怪&#xff0c;当出现性能问题时&#xff0c;如何进行优化&#xff0c;有以下一些操作&#xff1a; 1. 优化索引 1.1 添加索引 当项目中没有索引时&#xff0c;此时就该考虑添加索引。 当 sql 语句中 where 条件的关键字段…

【Kafka】Zookeeper集群 + Kafka集群

Zookeeper 概述 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制★★★ Zookeeper从设计模式角度来理解&#xff1a; 1&#xff09;是一个基于观察者模式设计的分布式服务管理框架&#xff1b; 它负责存储和管理大家都关…

AI大模型创新交汇点:当AI遇见艺术

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

代码随想录算法训练营三刷 day48 |动态规划之 198打家劫舍 213打家劫舍II 337打家劫舍III

三刷day48 198.打家劫舍1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组 213.打家劫舍II情况一&#xff1a;考虑不包含首尾元素情况二&#xff1a;考虑包含首元素&#xff0c;不包含尾元素情况三&…

飞机降落(蓝桥杯)

文章目录 [蓝桥杯 2023 省 B] 飞机降落题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示回溯算法&#xff08;DFS&#xff09; [蓝桥杯 2023 省 B] 飞机降落 题目描述 N N N 架飞机准备降落到某个只有一条跑道的机场。其中第 i i i 架飞机在 T i T_{i} Ti​ 时刻…

LinkedList部分底层源码分析

JDK版本为1.8.0_271&#xff0c;以插入和删除元素为例&#xff0c;LinkedList部分源码如下&#xff1a; //属性&#xff0c;底层结构为双向链表 transient Node<E> first; //记录第一个结点的位置 transient Node<E> last; //记录最后一个结点的尾元素 transient …

域控软件安全隔离关键技术剖析:MCU域 VS SOC域

安全隔离的需求 功能安全开发中&#xff0c;软件阶段由软件V模型左边的软件安全需求SSR开始。SSR是从技术安全需求TSR中提取出软件的功能安全需求&#xff0c;大多数情况下具有不同的ASIL等级。 图1 功能安全软件开发V模型 随后&#xff0c;软件安全需求会被分配到软件架构中的…

【无标题】Python中的函数——简洁与高效的编程之美

Python中的函数——简洁与高效的编程之美 在Python编程中&#xff0c;函数是一个非常重要的概念。它们不仅使代码更加简洁易读&#xff0c;还提高了代码的重用性和可维护性。本文将详细介绍Python中的函数&#xff0c;包括其定义、调用、参数传递、返回值以及函数的一些高级特…

AcWing-滑动窗口

单调队列模板题&#xff1a; 所需知识&#xff1a;单调队列 利用双端队列来实现单调队列&#xff1b; 双端队列与普通队列的不同处&#xff1a;双端队列删除元素时既可以删除队头又可以删掉队尾&#xff0c;其可以较好的维护单调队列的单调性&#xff1b; 双端队列的定义及…

蓝桥杯-数组切分

问题描述 已知一个长度为 N 的数组: A1,A2,A3,...AN 恰好是1~ N的一个排列。现 在要求你将 4 数组切分成若干个 (最少一个,最多 N 个)连续的子数组,并且 每个子数组中包含的整数恰好可以组成一段连续的自然数。 例如对于 4 1,3,2,4,一共有 5 种切分方法: 1324:每个单独的数显然…

卫星影像联合无人机实现农业保险全生命周期监管监测

随着科技的进步&#xff0c;农业保险监管系统的发展日新月异。特别是近年来&#xff0c;随着卫星技术与无人机技术的结合&#xff0c;为农业保险监管系统带来了前所未有的革新。本文将深入探讨如何利用卫星与无人机方案构建高效的农业保险监管系统&#xff0c;并结合实例进行说…

迷宫-蓝桥602-bfs-2019省赛

代码 #include<iostream> #include<queue> using namespace std;struct node{int x,y;string path; }; char mp[31][51]; char k[]{D,L,R,U};//要走的路径标记 int dir[4][2]{{1,0},{0,-1},{0,1},{-1,0}};//wangleba int vis[30][50];//记录走没走void bfs(){node…

Dart 中 JS 互操作的历史

由于在 Dart 3.3 中达到了令人兴奋的 JavaScript 互操作里程碑&#xff0c;Wasm 的支持刚刚登陆当前的 Flutter 测试版。为了庆祝这一里程碑&#xff0c;我们回顾了 Dart 和 JavaScript 互操作性长达十年的历程。 从 Dart 诞生之初&#xff0c;互操作性就是一个核心重点。2011…

【笔记】Teach less, learn more

文章目录 核心概念针对学习的理念知识高级知识生成器&#xff1a;WHWM四问基础知识与导出知识 案例小学数学小学语文经济学教育学物理学 核心概念 学习方法&#xff1a;以学科大图景为目标的以批判性思维和系联性思考为指导的理解型学习。 学科大图景&#xff1a;一个学科大概…

hashmap ArrayList基础使用 java

hashmap 1 hashmap1.1 hashmap基础使用1.2 遍历hashmap 2 ArrayList2.1 ArrayList基础使用2.2 ArrayList遍历 |--> Collection接口: 单列集合&#xff0c;用来存储一个一个对象。--> list接口:存储有序&#xff0c;可重复的数据。--> ArrayList、LinkList vector-->…

golangci-lint 报错

File is not gci-ed with --skip-generated -s standard,default (gci) golangci-lint 报错上面的错解决办法&#xff1a; 1. 文件换行需要换成"LF" 而不是"CRLF" ---->>> 我用的goland IDE&#xff0c;随便在这个文件删除一个空行&#xff…

20240412,引用,函数高级

老子什么时候能找到一个很爱我还和我一样喜欢看日出日落的对象 一&#xff0c;引用 给变量起别名&#xff0c;数据类型 & 别名原名&#xff1b;引用一定要初始化&#xff0c;初始化之后不能更改 #include <iostream> using namespace std; int main() {int a 10;i…

基于STM32技术的智慧超市系统研究

基于STM32技术的智慧超市系统研究 **摘要&#xff1a;**随着物联网技术的飞速发展&#xff0c;智慧超市作为零售业的一种新兴模式&#xff0c;正越来越受到关注。本文以STM32技术为基础&#xff0c;设计并实现了一套智慧超市系统。论文详细介绍了系统的架构、功能设计以及实现…

导入导出之使用EasyExcel快速进行表格导出

使用 EasyExcel 快速进行表格导入导出操作 在日常工作中&#xff0c;表格的导入和导出是常见的需求。针对这种情况&#xff0c;EasyExcel 提供了便捷的解决方案&#xff0c;可以快速地实现 Excel 表格的导入和导出操作。本文将介绍如何使用 EasyExcel 进行表格导出&#xff0c…

Linux权限的讲解

目录 1、用户的分级 2、用户的身份 3、文件的权限属性 3.1 文件类型 4、chmod 4.1 用八进制形式更改权限 5、chown与chgrp 6、umask 结语 前言&#xff1a; 在Linux下虽然一切都是文件&#xff0c;但是由于文件的权限不一样&#xff0c;导致访问或更改文件存在局限性…