- 前端
- 开发工具
【免费下载链接】beautiful-react-hooks
🔥 A collection of beautiful and (hopefully) useful React hooks to speed-up your components and hooks development 🔥
本文围绕 beautiful-react-hooks 官方文档(日文版 README)展开,完整覆盖该库的设计动机、安装方式、全部 Hook 清单、Peer 依赖策略与贡献流程,并结合当前仓库源码剖析useInterval、useDebouncedCallback、createStorageHook等代表性 Hook 的实现原理与测试体系。读完后,你将能够在本项目中快速定位并使用所需的 Hook,理解其底层实现约定,并按项目规范进行测试与文档补充。
设计动机:为什么需要 beautiful-react-hooks
根据 docs/README.jp-JP.md 中的「なぜ?(为什么)」章节,作者长期维护并跨项目共享的自定义 Hook 呈现出高度相似的共性:大量涉及回调引用(callback references)、事件绑定与组件生命周期管理。这些重复模式被抽象沉淀为beautiful-react-hooks这一 Hook 集合,目的是帮助企业和专业开发者加速开发流程。
文档中还强调了两个关键设计理念:
- 简练且具体的 API:优先考虑代码可读性;
- 尽可能降低学习曲线:让更大的团队能够使用并共享同一套 Hook。
文档同时给出一条醒目的提示:使用任何 Hook 之前,请务必先阅读其对应文档(每个 Hook 在docs/目录下都有独立文档页,例如 useInfiniteScroll 文档)。
从仓库结构看,项目以 TypeScript 编写,每个 Hook 一个独立源文件(src/useXxx.ts),配套独立测试(test/useXxx.spec.js)与独立文档(docs/useXxx.md),这种"一 Hook 一文件"的组织方式正是其"低学习曲线"理念在工程结构上的体现。
核心特性
原文档「特徴(特性)」一节列出了三点核心特性:
- 简练的 API(簡潔な API);
- 轻量(軽量):库运行时仅依赖两个 lodash 工具函数,其余能力全部基于 React 原生 Hook 与浏览器 Web API 实现;
- 易学习(学習が容易)。
"轻量"这一点可以直接从 package.json 中得到验证:dependencies中只有两个依赖:
"dependencies": { "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1" }而所有 Hook 均以独立入口按需提供(见下文"按需引入与双模块支持"),这意味着未使用的 Hook 不会进入你的打包产物。
安装
按照原文档「インストール(安装)」章节,支持 npm 与 yarn 两种方式:
使用npm:
$ npm install beautiful-react-hooks使用yarn:
$ yarn add beautiful-react-hooks环境前提:Peer Dependencies 策略
原文档「Peer dependencies」章节指出:部分 Hook 构建于第三方库(rxjs、react-router-dom 等)之上,因此它们被声明为 peer dependencies;如果你不直接使用这些 Hook,则无需安装这些依赖。
package.json 中实际声明的 peer 依赖范围为:
"peerDependencies": { "react": ">=18.2.0 <20.0.0", "react-dom": ">=18.2.0 <20.0.0", "react-router-dom": ">=5.0.0", "rxjs": ">=7.0.0" }可以推断出对应的依赖关系:
react/react-dom >=18.2.0 <20.0.0:库运行于 React 18+ 环境;react-router-dom >=5.0.0:服务于 URL 查询类 Hook(useQueryParam、useQueryParams、useSearchQuery、useURLSearchParams);rxjs >=7.0.0:服务于响应式类 Hook(如useObservable)。
按需安装的收益与exports映射(见下节)共同构成了该库的"轻量"承诺:只引入你实际使用的那一个 Hook 的入口。
基本用法与按需引入
英文版主 README 给出了基本用法示例(日文版文档在 Hook 链接中对应同类说明):
import useSomeHook from 'beautiful-react-hooks/useSomeHook'package.json 中的exports字段为每个 Hook 都定义了独立的子路径导出,并同时提供ESM、CJS 与类型声明三种产物。以useInterval为例:
"./useInterval": { "import": "./dist/esm/useInterval.js", "require": "./dist/useInterval.js", "types": "./dist/useInterval.d.ts" }这意味着:
- ESM 打包器(Vite、现代 Webpack)走
dist/esm/下的 ESM 产物; - CommonJS 环境走
dist/下的 CJS 产物; - TypeScript 用户自动获得对应
.d.ts类型。
构建脚本(build-cjs、build-esm、build-types)分别对应 tsconfig.cjs.json、tsconfig.esm.json、tsconfig.types.json 三套 tsconfig,与上述三套产物一一对应。
仓库 usage_example.png 中展示的官方示例代码,演示了在一个组件中组合使用useMouse、useGeolocation、useMediaQuery、useWindowResize的典型场景:
import React, { useRef } from 'react'; import useMediaQuery from 'beautiful-react-hooks/useMediaQuery' import useGeolocation from 'beautiful-react-hooks/useGeolocation' import useWindowResize from 'beautiful-react-hooks/useWindowResize' import useMouse from 'beautiful-react-hooks/useMouse' const SomeComponent = () => { const ref = useRef() const mouse = useMouse(ref) // 元素被悬停时返回鼠标信息 const [geoState, { onChange }] = useGeolocation() // 返回地理位置信息 const isTablet = useMediaQuery('(max-width: 48rem)') // 组件内媒体查询 const onWindowResize = useWindowResize() onWindowResize(() => { trackWindowSize() }) return ( <main className="some-component" ref={ref}> {isTablet ? <TabletNavigation /> : <DesktopNavigation />} {/* ...根据 geoState.isRetrieving / isSupported / position 渲染... */} </main> ) }Hook 全量目录(按主题分类)
原文档「Hooks」一节共列出约 50 个 Hook。为保证文章可读性,以下在完整继承清单的前提下按主题重新编排,所有链接均指向仓库内的独立文档页:
状态管理类
- useMutableState
- useDefaultedState
- useObjectState
- useValidatedState
- usePreviousValue
- useValueHistory
- useToggle
- useRenderInfo
事件与回调类
- useEvent
- useGlobalEvent
- useDebouncedCallback
- useThrottledCallback
- useMouse、useMouseState、useMouseEvents
- useTouch、useTouchState、useTouchEvents
- useSwipe、useHorizontalSwipe、useVerticalSwipe
- useSwipeEvents
- useLongPress
- useDrag、useDropZone、useDragEvents
生命周期类
- useLifecycle
- useDidMount
- useWillUnmount
- useUnmount
- useUpdateEffect
- useIsFirstRender
时间类
- useTimeout
- useInterval
- useConditionalTimeout
- useRequestAnimationFrame
浏览器环境与存储类
- useMediaQuery
- useOnlineState
- useViewportSpy
- useViewportState
- useWindowResize
- useWindowScroll
- useResizeObserver
- useMutationObserver
- useInfiniteScroll
- useLocalStorage
- useSessionStorage
- useCookie
- useDarkMode
地理位置与语音类
- useGeolocation、useGeolocationState、useGeolocationEvents
- useSpeechRecognition、useSpeechSynthesis、useSystemVoices
其他
- useObservable
- useAudio
- useQueryParam、useQueryParams、useSearchQuery、useURLSearchParams
源码级实现剖析
以下选取几个代表性 Hook,结合源码说明该库的实现约定,帮助读者理解其 API 背后的行为边界。
useGeolocation:组合式 Hook 的"快捷方式"
useGeolocation 本身不做任何底层工作,它是useGeolocationState与useGeolocationEvents的组合快捷方式:
const useGeolocation = (options: PositionOptions = geoStandardOptions) => { const state = useGeolocationState(options) const events = useGeolocationEvents(options) return [state, events] as [UseGeolocationStateResult, UseGeolocationEventsResult] }可以推断出该库的一个设计模式:状态读取与事件绑定分离——多数交互类 Hook(Geolocation、Mouse、Touch、Drag)都提供State(只读状态)与Events(回调设置器)两种形态,并再提供一个聚合版本,让调用方按粒度自选。
useInterval:毫秒参数变化即重置定时器
useInterval 的签名是(fn, milliseconds, options),其中options支持cancelOnUnmount(默认true)。其实现要点:
const useInterval = <TCallback extends GenericFunction> (fn: TCallback, milliseconds: number, options: UseIntervalOptions = defaultOptions) => { // ... // when the milliseconds change, reset the timeout useEffect(() => { if (typeof milliseconds === 'number') { timeout.current = setInterval(() => { callback.current() }, milliseconds) } return clear }, [milliseconds]) // when component unmount clear the timeout useEffect(() => () => { if (opts.cancelOnUnmount) { clear() } }, []) return [isCleared, clear] as [boolean, () => void] }两个值得注意的实现细节:
- 回调通过
useRef保存最新引用(callback.current = fn),因此修改回调函数不需要重建定时器,只有milliseconds变化才会触发定时器重建——这是一个重要的行为边界; - 卸载时依据
cancelOnUnmount选项决定是否清除定时器,默认会清除,避免内存泄漏。
返回值是[isCleared, clear]:一个标记定时器是否已被清除的布尔值与手动清除函数。
useDebouncedCallback:默认 600ms 与 lodash 底座
useDebouncedCallback 的签名与默认值:
const useDebouncedCallback = <TCallback extends GenericFunction> (fn: TCallback, dependencies: DependencyList = [], wait: number = 600, options: DebounceOptions = defaultOptions) => { const debounced = useRef(debounce<TCallback>(fn, wait, options)) useEffect(() => { debounced.current = debounce(fn, wait, options) }, [fn, wait, options, ...dependencies]) useWillUnmount(() => { debounced.current?.cancel() }) return useCallback((...args: Parameters<TCallback>) => debounced.current(...args), [...dependencies]) }可确认的实现事实:
wait默认600ms(JSDoc 中另有一处注释写 250ms,以代码实际默认值 600 为准);options支持leading、trailing、maxWait三个 lodash debounce 选项,默认为leading: false, trailing: true;- 底层复用
lodash.debounce(这正是 package.json 中仅有的两个运行时依赖之一); - 组件卸载时自动
cancel()未执行的防抖调用,复用本库的 useWillUnmount。
useThrottledCallback 与之对应,底座是lodash.throttle。
createStorageHook:工厂模式生成存储 Hook
useLocalStorage 与 useSessionStorage 并非各自独立实现,而是由共享工厂 createStorageHook 生成。工厂的核心逻辑:
const createStorageHook = (type: 'session' | 'local') => { type SetValue<TValue> = (value: TValue | ((previousValue: TValue) => TValue)) => void const storageName: `${typeof type}Storage` = `${type}Storage` if (isClient && !isAPISupported(storageName)) { warnOnce(`${storageName} is not supported`) } return function useStorageCreatedHook<TValue>(storageKey: string, defaultValue?: any) { if (!isClient) { // SSR 下返回默认值与空函数,避免访问 window return [JSON.stringify(defaultValue) as unknown as TValue, noop] } // 客户端:读取 storage.getItem,JSON 安全解析, // setValue 支持函数式更新并同步写回 storage } }这段工厂代码集中体现了该库的几个通用工程约定:
- SSR 安全:非客户端环境直接返回默认值,开发模式下通过
warnOnce提示${storageName} could not be available during SSR; - 特性检测:isAPISupported 检查
window上是否存在对应存储 API,不支持时仅警告一次而非抛错; - JSON 安全读写:配合 safelyParseJson 与 try/catch 包裹的
setItem,存储读写失败时静默降级; - 函数式更新支持:
setValue兼容setValue(v => v + 1)风格的 updater。
该工厂位于 src/factory 目录,与 createHandlerSetter 一起,印证了"用少量工厂抽象重复模式"的实现思路。
共享工具层:src/shared
所有 Hook 共用的判断与工具函数集中在 src/shared 目录,例如:
- isClient.ts:环境判断;
- isAPISupported.ts:
api in window特性检测; - warnOnce.ts:警告去重;
- safelyParseJson.ts:安全 JSON 解析;
- geolocationUtils.ts:地理位置标准选项
geoStandardOptions(useGeolocation的默认参数即来源于此); - swipeUtils.ts:滑动判断共享逻辑,服务
useSwipe/useHorizontalSwipe/useVerticalSwipe三兄弟。
测试体系与质量保障
原文档「利用しているライブラリ(使用的库)」章节列出:React、Mocha、Chai、@testing-library/react、@testing-library/react-hooks。结合 package.json 可确认完整的测试与工具链:
"scripts": { "lint": "eslint src/ --ext .ts", "test": "nyc mocha --recursive --exit \"./test/**/*.spec.+(js|jsx)\"", "build": "npx del-cli dist && npm run build-cjs && npm run build-esm && npm run build-types", "start": "npx styleguidist server" }- 测试运行器为Mocha + Chai,覆盖率由nyc统计;DOM 模拟使用jsdom,时间/异步行为使用sinon;
- 针对浏览器 API 的测试依赖 test/mocks 目录下的手工 Mock:如 AudioApi.mock.js、GeoLocationApi.mock.js、ResizeObserver.mock.js、SpeechSynthesis.mock.js 等;
- 每个 Hook 都有独立规格文件(
test/useXxx.spec.js),与源码文件一一对应; - 文档站由react-styleguidist驱动(
npm start启动本地文档服务器,build-doc会先执行 scripts/generate-doc-append-types.js 将类型信息附加进文档再构建静态站)。
贡献指南
原文档「コントリビューション(贡献)」章节明确:
- 提交 PR 前必须阅读 CONTRIBUTING 指南;
- 必须为代码编写测试,并在提交前运行
npm test与npm build确认无问题; - 若新增自定义 Hook,必须补充文档,可使用 HOOK_DOCUMENTATION_TEMPLATE 作为文档模板。
补充说明 CONTRIBUTING 与 HOOK_DOCUMENTATION_TEMPLATE 均位于仓库根目录,可直接查看。
总结
beautiful-react-hooks 的价值主张可以归纳为:约 50 个覆盖状态、事件、生命周期、时间、浏览器环境、存储、地理位置与语音等主题的开箱即用 React Hook,配合"一 Hook 一文档一测试"的工程组织、仅两个 lodash 运行时依赖、ESM/CJS 双产物与逐 Hook 的exports按需引入,为 React 团队提供了一条低学习曲线的业务逻辑抽象路径。上手路径建议:先在本文"Hook 全量目录"中定位目标 Hook,打开其docs/useXxx.md文档确认 API 签名与默认值,再参考 src/shared 与对应源码文件理解边界行为,最后按 CONTRIBUTING 流程为使用或扩展该 Hook 的代码补上测试与文档。
- 前端
- 开发工具
【免费下载链接】beautiful-react-hooks
🔥 A collection of beautiful and (hopefully) useful React hooks to speed-up your components and hooks development 🔥
相关推荐
beautiful-react-hooks:一套轻量级 React 自定义 Hooks 集合的安装、选型与实现原理
beautiful react hooks:一套轻量级 React 自定义 Hooks 集合的安装、选型与实现原理 本文以 beautiful react ho
前端开发工具解析 beautiful-react-hooks 的 HOOK_DOCUMENTATION_TEMPLATE:如何为自定义 React Hook 编写规范文档
解析 beautiful react hooks 的 HOOK_DOCUMENTATION_TEMPLATE:如何为自定义 React Hook 编写规范文档
前端开发工具React Hooks完全指南:从useState到自定义Hook实战
React Hooks完全指南:从useState到自定义Hook实战 引言:为什么React Hooks彻底改变了组件开发? 你是否曾在React类组件中挣扎
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考