写在前面
当你对某一个业务场景有自己的理解,想提炼开发了一个很好用的组件,想开放给别的同学使用,或者甚至放在社区给任何一个人使用,你应该会产生以下疑问:
- 一个标准的组件是怎么样的,在开发过程中有哪些注意事项?
- 组件打包过程是怎样的,如何按需加载?
- 别人要如何没有负担的使用我的组件?
- 组件如何支持同步和异步使用?
- 如何更好的维护独立组件库?
- …
基于以上疑问,我们开始这篇阅读这篇文章
组件设计
如何让别人清晰的使用你的组件,换位思考一下,你在使用别人的组件的时候,什么样的组件是你用起来十分符合预期甚至超出预期的?什么样的组件让你在用的时候,充满问号,产生“不如自己写一个”的想法?
首先要让使用者知道你的组件的主要功能(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 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}.../>
受控组件与非受控组件
受控组件就是支持被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 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 '组件库';
组件调试
开发阶段可以配合文档工具,例如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等特点是衡量一个组件是否好用的最重要的指标。
好了,如此我们就完成了对一个组件从开发到发布、再到使用的全部过程,是不是很简单呢?快去开发属于你自己的公共组件吧。
最后
为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。
有需要的小伙伴,可以点击下方卡片领取,无偿分享