我开源了一个 React 组件库,沉淀了多个高频组件和实用 Hooks
大家好,我是木木剑光。
在业务开发过程中,我们经常会遇到一些重复性的需求和场景。比如瀑布流布局、无限滚动加载、竞态请求处理等等。每次都要重新写一遍类似的逻辑,不仅浪费时间,还容易出现 bug。
为了解决这个问题,我将团队在研发过程中沉淀下来的一些通用组件和 Hooks 整理成了一个开源组件库 ------ @mmjg/ui-components。今天想和大家分享一下这个库的设计思路和核心功能。
项目背景
在实际的业务场景中,我们发现有几类需求特别高频:
- 瀑布流布局:图片展示、商品列表等场景,需要高性能的虚拟滚动和响应式布局
- 3D 交互效果:一些创新性的 UI 交互,比如 3D 旋转菜单
- 流光效果:loading 状态的视觉优化
- 通用 Hooks:无限加载、元素尺寸监听、竞态请求处理等
这些需求看似简单,但要做得好、做得通用,还是有不少细节需要考虑的。比如瀑布流组件,需要考虑虚拟滚动性能、响应式布局、动态高度计算、滚动位置恢复等等。
核心组件介绍
1. Masonry 瀑布流组件
瀑布流布局在前端开发中非常常见,但要实现一个高性能、功能完善的瀑布流组件并不容易。我们的 Masonry 组件具有以下特性:
核心功能:
- 虚拟滚动优化:只渲染可视区域内的元素,大幅提升性能
- 响应式布局:支持断点配置,根据容器宽度自动调整列数
- 无限加载:内置加载更多机制,支持阈值配置
- 动态高度计算:自动计算每个 item 的高度,支持最小高度和额外高度配置
- 滚动控制:支持滚动到顶部、滚动到指定元素等 API
关键实现原理:
虚拟滚动的核心思路是:计算可视区域的范围,只渲染这个范围内的元素。我们通过 useScrollViewArea hook 来实现:
typescript
const [viewRange, reCalcViewRange] = useScrollViewArea({
container: () => containerRef.current!,
overscanHeight: 3000, // 预渲染区域
loadMoreThreshold,
onLoadMore,
});
这样,即使有成千上万条数据,也能保持流畅的滚动性能。
使用示例:
typescript
import { Masonry } from '@mmjg/ui-components';
<Masonry
items={images}
columnCount={4}
gap={8}
itemRender={({ item, columnWidth, height }) => (
<img
src={item.src}
style={{ width: columnWidth, height }}
/>
)}
onLoadMore={(check) => {
// 加载更多数据
loadMoreData().then(() => {
check(); // 检查是否需要继续加载
});
}}
/>
2. Menu3D 三维菜单组件
这是一个创新的 3D 旋转菜单组件,支持鼠标拖拽交互。它的实现原理是通过 CSS 3D transform 和自定义 hook 实现的。
核心思路:
- 使用
rotateY和translateZ将菜单项分布在 3D 空间中 - 通过
useRotateAnimationhook 管理旋转动画 - 通过
useMouseDragOffsethook 处理鼠标拖拽交互
typescript
export function Menu3D(props: Menu3DProps) {
const { menus, rotateRadius = 200 } = props;
const itemRotateDeg = 360 / menus.length;
return (
<div className={styles["component-menu3d"]}>
<div className={styles["component-menu3d-rotate"]}>
{menus.map((menu, index) => (
<div
key={menu.id}
style={{
transform: `rotateY(${itemRotateDeg * index}deg)
translateZ(${rotateRadius}px)`
}}
onClick={() => onMenuClick(menu.id)}
>
{/* 菜单内容 */}
</div>
))}
</div>
</div>
);
}
3. Shimmer 流光效果组件
这是一个简单但实用的流光效果组件,支持文本和容器两种模式。
typescript
<Shimmer type="text" duration={2}>
加载中...
</Shimmer>
<Shimmer type="container" width={10} color="#ffffff">
<div style={{ width: 200, height: 100 }}>
内容区域
</div>
</Shimmer>
实用 Hooks 集合
除了 UI 组件,我们还沉淀了一些高频使用的 Hooks。
useAutoLoadMore:自动加载更多
这是一个解决无限加载场景痛点的 hook。
问题场景: 分页的 pageNum 是一个定值,但窗口的大小用户可以自由拖拽,因此可能出现窗口被拖拽得较大的情况下,滚动条消失,导致用户无法触发加载更多。
解决方案: 监听滚动容器大小变化,当滚动容器滚动条消失时,如果还有更多则触发加载更多。
typescript
useAutoLoadMore(containerRef.current, {
hasMore: hasMoreData,
onLoadMore: (check) => {
loadMoreData().then(() => {
check(); // 检查是否需要继续加载
});
}
});
useBoxSizeObserver:监听元素尺寸
基于 ResizeObserver 实现的 hook,可以方便地监听元素尺寸变化。
typescript
const [ref, boxSize] = useBoxSizeObserver();
return (
<div ref={ref}>
宽度: {boxSize.width}, 高度: {boxSize.height}
</div>
);
UniqueRequest:竞态请求处理
这是一个通用模块,用于解决前端开发中常见的竞态问题。
问题场景: 在搜索场景中,用户快速输入时可能会发起多个请求,后面的请求可能先返回,导致显示的结果不是最新的。
解决方案: 使用 AbortController 取消之前的请求,只保留最新的请求结果。
typescript
const uniqueRequest = new UniqueRequest();
// 在搜索场景中使用
const handleSearch = async (keyword: string) => {
try {
const result = await uniqueRequest.request(
searchAPI,
{ keyword }
);
setSearchResult(result);
} catch (error) {
if (isAbortError(error)) {
// 请求被取消,不做处理
return;
}
// 处理其他错误
}
};
技术栈和工程化
这个组件库基于以下技术栈:
- React 18+:使用稳定的 React 特性
- TypeScript:完整的类型支持
- Rolldown:使用下一代打包工具,构建速度更快
- Less:样式解决方案
在工程化方面,我们做了以下考虑:
- 类型安全:所有组件和 hooks 都有完整的 TypeScript 类型定义
- Tree Shaking:支持按需引入,减少打包体积
- 开发体验:支持 development 和 production 两种模式,开发时直接引用源码,生产环境使用构建产物
使用方式
组件库已经发布到 npm,可以直接安装使用:
bash
pnpm add @mmjg/ui-components
更多详尽的使用指南请查看我们的官方文档
总结
@mmjg/ui-components 是我们在实际业务开发中沉淀下来的组件库,它解决了我们遇到的一些常见问题。虽然目前功能还不够完善,但我们会持续迭代和优化。
开源这个项目的目的,一方面是希望能帮助到有同样需求的开发者,另一方面也希望能收到社区的反馈,让这个库变得更好。
如果你对这个项目感兴趣,欢迎查看我们的文档和源码,也欢迎提出 Issue 和 PR。
相关链接: