如何开发一个好用的公共组件

写在前面

当你对某一个业务场景有自己的理解,想提炼开发了一个很好用的组件,想开放给别的同学使用,或者甚至放在社区给任何一个人使用,你应该会产生以下疑问:

  • 一个标准的组件是怎么样的,在开发过程中有哪些注意事项?
  • 组件打包过程是怎样的,如何按需加载?
  • 别人要如何没有负担的使用我的组件?
  • 组件如何支持同步和异步使用?
  • 如何更好的维护独立组件库?

基于以上疑问,我们开始这篇阅读这篇文章

组件设计

如何让别人清晰的使用你的组件,换位思考一下,你在使用别人的组件的时候,什么样的组件是你用起来十分符合预期甚至超出预期的?什么样的组件让你在用的时候,充满问号,产生“不如自己写一个”的想法?

首先要让使用者知道你的组件的主要功能README.md),其次知道怎么用能满足自己的需求(props)。

一个好的README.md应该像antdelementUI的官方文档那样,写清楚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

如果你的组件是基于elementUIantd等底层组件库做了进一步封装,满足更强大的定制需求,不要忘了保留底层组件的原有功能。

  • Props写法:自定义Props extends 底层组件Props(可以通过lib单独引入Props类型)
//此处引入只是为了方便解释,实际项目并非如此,请自行查阅相关组件源码以及文档
import { SelectProps } from 'antd/next/types/select';
import { ISelection } from 'antd/lib/hooks/select/hook';
// 定义props
export interface IEmployeeSelectPropsextends 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}.../> 

受控组件与非受控组件

受控组件就是支持被Reactstate控制的组件,简单来讲,可以通过下传valueonChange控制组件值。同理反推,非受控组件没有和Reactstate绑定,只能通过绑定 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 PageProductfrom '组件';

// 单独引入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 '组件库'; 

组件调试

开发阶段可以配合文档工具,例如dumivitePress等静态站点工具,可以在开发过程中调试,所见即所得。

打包构建

开发完成后,利用打包工具打包构建生成生产文件。

单元测试

如果你的组件逻辑十分复杂,依赖异步数据返回有不同的表现,那么最好添加单元测试,保证每次正式发布之前。新的迭代不会影响老的功能。单元测试推荐使用Jest

组件发布

修改package.json配置文件,登陆npm账号将组件发布至npm.

// 控制台会返回下一个小版本号 如v1.0.1 
npm version patch 
// 重新发布 
npm publish 

组件使用

组件在打包构建的时候,通过不同的配置会形成不同的产物,当然也会有不同的引入方式。“同步引入”是指消费方直接npm install package ,在项目代码库中通过ES Module的方式引入使用。“异步引入”方式通常通过请求一个独立的js文件来加载并使用组件。

通常来讲,如果是独立组件,且不是首屏强依赖组件,推荐使用异步加载方式。如果是组件库,最好使用同步加载方式,通过ESM语法进行Tree Shaking,实现按需引入。

总结

组件是对我们最常用的场景的一些提炼,是为了让我们可以快速开发出想要的功能,而无需再从头开始,因此通用性、可扩展性、使用便捷简单、完善的API等特点是衡量一个组件是否好用的最重要的指标。

好了,如此我们就完成了对一个组件从开发到发布、再到使用的全部过程,是不是很简单呢?快去开发属于你自己的公共组件吧。

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值