我开源了一个 React 组件库,沉淀了多个高频组件和实用 Hooks

我开源了一个 React 组件库,沉淀了多个高频组件和实用 Hooks

大家好,我是木木剑光。

在业务开发过程中,我们经常会遇到一些重复性的需求和场景。比如瀑布流布局、无限滚动加载、竞态请求处理等等。每次都要重新写一遍类似的逻辑,不仅浪费时间,还容易出现 bug。

为了解决这个问题,我将团队在研发过程中沉淀下来的一些通用组件和 Hooks 整理成了一个开源组件库 ------ @mmjg/ui-components。今天想和大家分享一下这个库的设计思路和核心功能。

项目背景

在实际的业务场景中,我们发现有几类需求特别高频:

  1. 瀑布流布局:图片展示、商品列表等场景,需要高性能的虚拟滚动和响应式布局
  2. 3D 交互效果:一些创新性的 UI 交互,比如 3D 旋转菜单
  3. 流光效果:loading 状态的视觉优化
  4. 通用 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(); // 检查是否需要继续加载
        });
    }}
/>

这是一个创新的 3D 旋转菜单组件,支持鼠标拖拽交互。它的实现原理是通过 CSS 3D transform 和自定义 hook 实现的。

核心思路:

  1. 使用 rotateYtranslateZ 将菜单项分布在 3D 空间中
  2. 通过 useRotateAnimation hook 管理旋转动画
  3. 通过 useMouseDragOffset hook 处理鼠标拖拽交互
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:样式解决方案

在工程化方面,我们做了以下考虑:

  1. 类型安全:所有组件和 hooks 都有完整的 TypeScript 类型定义
  2. Tree Shaking:支持按需引入,减少打包体积
  3. 开发体验:支持 development 和 production 两种模式,开发时直接引用源码,生产环境使用构建产物

使用方式

组件库已经发布到 npm,可以直接安装使用:

bash 复制代码
pnpm add @mmjg/ui-components

更多详尽的使用指南请查看我们的官方文档

总结

@mmjg/ui-components 是我们在实际业务开发中沉淀下来的组件库,它解决了我们遇到的一些常见问题。虽然目前功能还不够完善,但我们会持续迭代和优化。

开源这个项目的目的,一方面是希望能帮助到有同样需求的开发者,另一方面也希望能收到社区的反馈,让这个库变得更好。

如果你对这个项目感兴趣,欢迎查看我们的文档源码,也欢迎提出 Issue 和 PR。


相关链接:

相关推荐
凤山老林2 小时前
从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然
前端·人工智能·状态模式
777VG9 小时前
PostgreSQL +martin将多张表输出成一个 MVT
前端·数据库·postgresql
mayaairi9 小时前
JS循环语句深度解析:嵌套for、while与do...while
开发语言·前端·javascript
To_OC9 小时前
啃完流式输出:从一个卡顿的 LLM 接口开始,我搞懂了数据流到底怎么 “流”
前端·javascript·llm
阳光是sunny10 小时前
LangGraph实战教程:defer延迟节点——让收尾工作自动排到最后
前端·人工智能·后端
kyriewen10 小时前
我用了三周Claude Code Skills——总结出5条铁律,第3条最反直觉
前端·ai编程·claude
阳光是sunny10 小时前
LangGraph实战教程:控制流详解
前端·人工智能·后端
格尔曼Noah11 小时前
Safari浏览器中如何只允许指定网站下载
前端·safari
用户0595401744612 小时前
用了3年Redis,才发现我一直没搞懂缓存一致性测试
前端·css
swipe12 小时前
07|(前端转后全栈)为什么后端也要缓存?从前端缓存思维理解 Redis
前端·后端·全栈