写在前面
当你对某一个业务场景有自己的理解,想提炼开发了一个很好用的组件,想开放给别的同学使用,或者甚至放在社区给任何一个人使用,你应该会产生以下疑问:
基于以上疑问,我们开始这篇阅读这篇文章
组件设计
如何让别人清晰的使用你的组件,换位思考一下,你在使用别人的组件的时候,什么样的组件是你用起来十分符合预期甚至超出预期的?什么样的组件让你在用的时候,充满问号,产生“不如自己写一个”的想法?
首先要让使用者知道你的组件的主要功能(README.md
),其次知道怎么用能满足自己的需求(props
)。
一个好的README.md
应该像antd
、elementUI
的官方文档那样,写清楚props
用法,有相应的示例展示,必要的时候使用mock数据支持基本的样式展示。
举个例子(antd):
除此之外,第一次开发独立组件或者独立组件库,还应该注意什么呢?
清晰的types类型定义&导出
清晰的类型定义不仅能降低组件使用门槛,也能使组件减少大部分js错误(例如类型错误,取值为空错误等)。
export interface BaseButtonProps {
type?: ButtonType;
icon?: React.ReactNode;
/**
* Shape of Button
*
* @default default // ✅ 类型注释默认值
*/
shape?: ButtonShape;
size?: SizeType;
disabled?: boolean;
loading?: boolean | { delay?: number };
prefixCls?: string;
className?: string;
ghost?: boolean;
danger?: boolean;
block?: boolean;
children?: React.ReactNode; // ✅ 开放插槽
}
export type AnchorButtonProps = {
href: string;
target?: string;
onClick?: React.MouseEventHandler<HTMLElement>;
} & BaseButtonProps &
Omit<React.AnchorHTMLAttributes<any>, 'type' | 'onClick'>;
// ✅ 开放原生props
export type NativeButtonProps = {
htmlType?: ButtonHTMLType;
onClick?: React.MouseEventHandler<HTMLElement>;
} & BaseButtonProps &
Omit<React.ButtonHTMLAttributes<any>, 'type' | 'onClick'>;
export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>;
可扩展插槽(ReactNode)
提供可消费插槽,允许消费方使用自定义children
做进一步扩展。
// 扩展插槽
const {iconNode,kids} = props
let ButtonNode = (
<button
{...(rest as NativeButtonProps)}
type={htmlType}
className={classes}
onClick={handleClick}
disabled={mergedDisabled}
ref={buttonRef}
>
{iconNode}
{kids}
</button>
);
开放与消费底层组件Props
如果你的组件是基于elementUI
、antd
等底层组件库做了进一步封装,满足更强大的定制需求,不要忘了保留底层组件的原有功能。
Props
写法:自定义Props extends 底层组件Props(可以通过lib单独引入Props类型)
//此处引入只是为了方便解释,实际项目并非如此,请自行查阅相关组件源码以及文档
import { SelectProps } from 'antd/next/types/select';
import { ISelection } from 'antd/lib/hooks/select/hook';
// 定义props
export interface IEmployeeSelectProps
extends Omit<SelectProps, 'mode' | 'onChange'>,
Pick<ISelection, 'maxSelection' | 'mode' | 'onChange'> {
customPropsA?: string[];
customPropsB?: string[];
/**
* 自定义接口
*/
list?: QueryListInfoType;
...
}
- 如何接收并消费传入的
底层组件Props
:解构赋值
// 接收
const { customPropsA, customPropsB, ...rest } = props;
// 消费
<Select
{...rest}
onChange={customPropsA}
onSearch={customPropsB}
...
/>
受控组件与非受控组件
受控组件就是支持被React
的state
控制的组件,简单来讲,可以通过下传value
和onChange
控制组件值。同理反推,非受控组件没有和React
的state
绑定,只能通过绑定 Ref
单向获得组件值(ref.current.value
)。
因此,通常来讲,一个好用的公共组件需要支持受控模式和非受控模式,能够满足普通用户的最简使用需求(组件内部自己维护状态),也能够满足高级用户的自定义需求(由使用方传入state
值和onChange
方法)。
这里推荐使用ahooks的useControllableValue
,它支持父组件下传state,如果没有下传,则交由组件内部管理状态值
举个例子:
const [visible, onVisibleChange] = useControllableValue(props, {
trigger: 'onVisibleChange',
defaultValuePropName: 'defaultVisible',
valuePropName: 'visible',
});
样式隔离与CSS变量的使用
CSS没有作用域概念,引入即全局生效,但一个样式是否起作用由多个因素共同决定(重要程度、优先级、样式加载顺序)。组件使用者肯定不希望组件层级的样式影响到全局样式,为了避免样式冲突,我们就需要对样式进行隔离。
一般来讲,我们会在组件内部使用一个特定的前缀,例如element-ui
使用el-
作为前缀。
举例sass语法:
$css_prefix: $css-prefix: 'abc-module-' !default;
$content-border-color: rgba(159, 183, 249, 0.5);
.#{$css_prefix}button {
background-color: $content-border-color;
}
如果你想开发的是一组统一风格的组件库,那么推荐使用css变量,这样方便外部使用者通过改变css变量值的方式统一调整散落在页面各处的组件的样式风格。
组件多语言以及埋点
如果你的组件涉及国际化相关的,或想开放到社区,让各国的开发中都使用,那么组件的多语言处理是必不可少的,可以使用模版动态注入变量值,并且要设置好默认的兜底文案。
其次就是曝光埋点,可以让你清晰的看到组件的使用情况,可以更好的帮助你改进自己的组件,从而让更多的人使用它。
组件导出方式
组件导出方式取决于你想让使用方如何使用,如果你是单个独立组件,通过在入口文件以这样的方式导出:
import PageProduct from './components/you-module';
export { default as PageProduct } from './components/you-module';
export default PageProduct;
// 导出必要的types
export type { IProductProps } from './components/you-module';
// 消费方使用
import PageProduct from '组件';
// 单独引入types类型定义
import type IProductProps from '组件';
如果是组件库,可以这样导出:
export { default as PageProduct1 } from './components/you-module1';
export { default as PageProduct2 } from './components/you-module2';
// 消费方使用
import { PageProduct1,PageProduct2 } from '组件库';
组件调试
开发阶段可以配合文档工具,例如dumi
、vitePress
等静态站点工具,可以在开发过程中调试,所见即所得。
打包构建
开发完成后,利用打包工具打包构建生成生产文件。
单元测试
如果你的组件逻辑十分复杂,依赖异步数据返回有不同的表现,那么最好添加单元测试,保证每次正式发布之前。新的迭代不会影响老的功能。单元测试推荐使用Jest
组件发布
修改package.json
配置文件,登陆npm
账号将组件发布至npm
.
// 控制台会返回下一个小版本号 如v1.0.1
npm version patch
// 重新发布
npm publish
组件使用
组件在打包构建的时候,通过不同的配置会形成不同的产物,当然也会有不同的引入方式。“同步引入”是指消费方直接npm install package
,在项目代码库中通过ES Module
的方式引入使用。“异步引入”方式通常通过请求一个独立的js文件来加载并使用组件。
通常来讲,如果是独立组件,且不是首屏强依赖组件,推荐使用异步加载方式。如果是组件库,最好使用同步加载方式,通过ESM语法进行Tree Shaking,实现按需引入。
总结
组件是对我们最常用的场景的一些提炼,是为了让我们可以快速开发出想要的功能,而无需再从头开始,因此通用性、可扩展性、使用便捷简单、完善的API等特点是衡量一个组件是否好用的最重要的指标。
好了,如此我们就完成了对一个组件从开发到发布、再到使用的全部过程,是不是很简单呢?快去开发属于你自己的公共组件吧。
声明:本文仅供个人学习使用,来源于互联网,本文有改动,本文遵循[BY-NC-SA]协议, 如有侵犯您的权益,请联系本站,本站将在第一时间删除。谢谢你
原文地址:如何开发一个好用的公共组件