ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

React Aria 深度解析:react-spectrum 中可访问、自适应、国际化的 Hook 库如何构建无障碍 UI 基元

React Aria 深度解析:react-spectrum 中可访问、自适应、国际化的 Hook 库如何构建无障碍 UI 基元 React Aria 深度解析react-spectrum 中可访问、自适应、国际化的 Hook 库如何构建无障碍 UI 基元【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrumReact Ariareact-aria包是 react-spectrum 仓库中的 Hooks 库为设计系统提供一套无样式、可访问的 UI 基元。本文以 packages/react-aria/README.md 为主体结合包清单 packages/react-aria/package.json、导出索引 packages/react-aria/exports/index.ts 与核心 Hook 源码完整梳理 React Aria 的四项核心特性、useButton示例的逐行实现原理、47 个功能模块的版图以及包的工程化配置帮助你在自研设计系统中正确地使用并理解这套无障碍行为层。一、React Aria 的定位行为与可访问性而非渲染README 对 React Aria 的一句话定义是“A library of React Hooks that provides accessible UI primitives for your design system”一个为你的设计系统提供可访问 UI 基元的 React Hooks 库。它最重要的设计约束来自特性列表的最后一条——Fully customizable完全可定制React Aria 不实现任何渲染不强制 DOM 结构、样式方法或与设计绑定的细节只负责提供行为、可访问性与交互让你专注于自己的设计。在 react-spectrum 这个 monorepo 中React Aria 处于三层架构的中间层这一点可以从仓库根目录的 README.md 得到印证React Statelypackages/react-stately跨平台状态管理 Hooks可独立用于其他平台React Ariapackages/react-aria无样式的行为 可访问性 Hooks是本文主角React Spectrumpackages/adobe/react-spectrumAdobe Spectrum 设计系统的完整 React 实现构建在前两者之上。架构细节可进一步参阅 rfcs/2019-v3-architecture.md而 React Aria 与组合组件react-aria-components的关系则记录在 rfcs/2023-react-aria-components.md 中。包事实版本、依赖与 React 版本范围从 packages/react-aria/package.json 可以确认以下工程事实当前版本3.52.1项目内容包名 / 版本react-aria/3.52.1React 版本要求peerDependencies^16.8.0 \|\| ^17.0.0-rc.1 \|\| ^18.0.0 \|\| ^19.0.0-rc.1直接依赖react-stately3.50.0与 React Stately 版本锁定、react-types/shared、internationalized/date、internationalized/number、internationalized/string、clsx、aria-hidden、use-sync-external-store、swc/helpersSide Effectsfalse利于 tree-shaking这里有两个值得注意的点依赖react-stately说明了两者的分工useButton这类 Hook 只处理行为与 ARIA 属性而useCalendarState、useSliderState等状态管理来自 React StatelyREADME 结尾“Learn more”一节所指的 React Spectrum 与 React Stately 家族正是这一依赖关系的由来。internationalized/*三个依赖直接支撑了后文的国际化特性——日期date、数字number、字符串比较与大小写处理string分别对应 README 中提到的“internationalized date and number formatting”。二、四大核心特性逐项拆解README 的 Features 一节列出了四条特性下面逐一给出仓库内的源码证据。1. Accessible按 WAI-ARIA Authoring Practices 实现README 声称 React Aria 的可访问性与行为遵循 WAI-ARIA Authoring PracticesW3C 规范包括完整的屏幕阅读器与键盘导航支持。以按钮为例packages/react-aria/src/button/useButton.ts 中AriaBaseButtonProps接口暴露了完整的 ARIA 透传属性aria-disabled、aria-expanded、aria-haspopup支持menu | listbox | tree | grid | dialog等取值、aria-controls、aria-pressed含mixed态、aria-current说明 Hook 不仅生成 ARIA 属性还允许调用方覆盖。2. Adaptive统一的鼠标、触摸、键盘行为“Adaptive”特性的底层实现集中在 packages/react-aria/src/interactions 目录usePress点击、useKeyboard键盘、useHover悬停、useFocus/useFocusVisible聚焦与焦点可见性、useLongPress长按、useMove拖动移动、useContextMenu右键菜单等。useButton正是通过组合usePressuseFocusable得到跨输入方式一致的行为见后文。此外 packages/react-aria/src/visually-hidden/VisuallyHidden.tsx 提供了视觉隐藏但保留屏幕阅读器可读性的能力FocusScope/useFocusManager管理焦点陷阱如模态弹窗内的焦点循环。3. International34 个内置语言包README 声称支持“30 种以上语言”仓库中的intl/目录可以验证这一点packages/react-aria/intl/按组件划分为 20 个子目录autocomplete、calendar、menu、overlays等其中仅 packages/react-aria/intl/menu 一个目录就包含34 个语言包 JSONar-AE、de-DE、en-US、fr-FR、ja-JP、ko-KR、pt-BR、ru-RU、zh-CN、zh-TW等覆盖阿拉伯语RTL、斯拉夫语系、东亚语系。语言上下文的实现见 packages/react-aria/src/i18n/I18nProvider.tsx// Locale 结构locale 为 BCP47 语言码direction 由 isRTL(locale) 推导 export interface Locale { locale: string; direction: Direction; // rtl | ltr } export function I18nProvider(props: I18nProviderProps): JSX.Element { let {locale, children} props; // 显式传入 locale 时直接提供否则走 useDefaultLocale() 检测默认值 if (locale) { return I18nProviderWithLocale locale{locale} children{children} /; } return I18nProviderWithDefaultLocale children{children} /; }direction由isRTL(locale)自动推导这就是 README 中“right-to-left-specific behavior”的来源——阿拉伯语、希伯来语布局下组件行为会自动镜像。配套 Hooks 包括useCollator本地化排序、useDateFormatter、useNumberFormatter、useFilter本地化模糊匹配、useLocalizedStringFormatter读取上述intl/*.json语言包。4. Fully customizable不渲染任何 DOM从源码结构看绝大多数use*Hook 的返回值是{xxxProps}这样的属性袋调用方决定渲染哪个元素唯一的渲染型导出是FocusRing、Overlay、VisuallyHidden这类纯技术辅助组件。useButton的 TypeScript 重载useButton.ts#L145-L168支持button、a、div、input、span以及任意ElementTypeelementType默认button可传入RouterLink之类的自定义元素——这是“不强制 DOM 结构”承诺的直接体现。三、Getting StarteduseButton示例与源码级解读README 提供了一个“非常基础”的示例这是理解 React Aria 编程模型的最小闭环import {useButton} from react-aria/button; function Button(props) { let ref React.useRef(); let {buttonProps} useButton(props, ref); return ( button {...buttonProps} ref{ref} {props.children} /button ); } Button onPress{() alert(Button pressed!)}Press me/Button结合 packages/react-aria/src/button/useButton.ts 的实现可以明确每一行的职责入参是 props ref。useButton(props, ref)接收包含isDisabled、onPress/onPressStart/onPressEnd/onPressUp/onPressChange、onClick等的 props以及指向真实 DOM 节点的 ref。注意回调命名是抽象的onPress而非onClick——它统一了鼠标、触摸与键盘Enter/Space触发路径。返回值是{buttonProps, isPressed}ButtonAria接口buttonProps是需要展开到元素上的全部行为与 ARIA 属性isPressed暴露当前按压状态供调用方做视觉反馈。内部组装链useButton.ts#L177-L257// 简化自 useButton 实现 let {pressProps, isPressed} usePress({ onPressStart, onPressEnd, onPressChange, onPress, onPressUp, onClick, isDisabled, preventFocusOnPress, ref }); let {focusableProps} useFocusable(props, ref); let buttonProps mergeProps( focusableProps, pressProps, filterDOMProps(props, {labelable: true}) // 过滤掉 aria 等非 DOM 属性 );usePress负责统一各输入方式的按下生命周期useFocusable负责tabIndex与焦点行为mergeProps把多套事件处理器合并后写的处理器在前一个之后依次执行而非覆盖filterDOMProps确保抽象属性如onPress不会泄漏到 DOM 上触发 React 告警。自定义元素分支当elementType不是原生button时Hook 会补上role: button、按元素类型条件输出href/rel/disabled/aria-disabled例如a禁用时移除href以避免可聚焦的死链这解释了为什么 React Aria 能在任意元素上还原按钮的可访问语义。preventFocusOnPress选项源码注释中明确警告仅在提供了替代键盘交互如 ComboBox 的 MenuTrigger 或 NumberField 的增减按钮时才可开启是“行为层”思考的典型细节——它不是样式问题而是键盘可达性问题。该 Hook 的行为验证见 packages/react-aria/test/button/useButton.test.js交互演示见 packages/react-aria/stories/button/useButton.stories.tsx。四、模块版图47 个功能域与 97 个导出模块packages/react-aria/exports/index.ts 是 React Aria 的完整 API 面共 502 行导出。src/下共有 47 个功能目录按职责可以归纳为类别模块src/目录代表性导出基础交互interactionsusePress、useKeyboard、useHover、useFocusVisible、useFocusable按钮类buttonuseButton、useToggleButton、useToggleButtonGroup表单控件checkbox、radio、switch、toggle、slider、spinbutton、numberfield、textfield、searchfield、label、formuseCheckbox、useSlider、useTextField、useField选择器select、combobox、autocomplete、listbox、tokenfielduseSelect、useComboBox、useAutocomplete、useTokenField日期时间calendar、datepickeruseCalendar、useRangeCalendar、useDateField、useTimeField覆盖层overlays、dialog、disclosure、tooltip、menu、toastuseOverlay、useModal、usePopover、useOverlayTrigger、useMenuTrigger、useTooltip容器与结构table、grid、gridlist、tabs、tree、toolbar、breadcrumb(s)、separator、tag、actiongroupuseTable、useTabList、useTree、useBreadcrumbs选择与列表selectionListKeyboardDelegate拖放dnduseDrag、useDrop、useDraggableCollection、useDroppableItem、useClipboard焦点管理focusFocusScope、FocusRing、useFocusManager国际化工具i18nI18nProvider、useLocale、useDateFormatter、useFilter、isRTL其他color调色板组件群、progress、meter、live-announcer、visually-hidden、virtualizer、ssruseColorWheel、useProgressBar、SSRProvider、useIsSSR工具函数utils、collectionsmergeProps、chain、mergeRefs、useId、Collection包同时提供子路径导出package.json的exports[./*]所以import {useButton} from react-aria/button这样的深路径导入是受支持的类型方面exports/index.ts后半部分第 176 行至文件尾导出了每个 Hook 的Aria*Props输入接口与*Aria结果接口调用方可据此获得完整类型推断。值得指出的边界aria-modal-polyfill、ssr等目录表明库对模态语义兼容与 SSR 场景有专门处理SSRProvider/useIsSSR可在服务端渲染时切换行为UNSAFE_PortalProvider这类带前缀的导出则提示其为内部 API非稳定承诺。五、包的工程化细节exports 映射与构建产物packages/react-aria/package.json 的exports字段展示了该包如何同时服务不同消费场景{ main: ./dist/exports/index.cjs, module: ./dist/exports/index.js, exports: { .: { source: ./exports/index.ts, types: ./dist/types/exports/index.d.ts, import: ./dist/exports/index.mjs, require: ./dist/exports/index.cjs }, ./i18n/*: { types: ./i18n/lang.d.ts, import: ./i18n/*.mjs, require: ./i18n/*.js }, ./*: { source: ./exports/*.ts, types: ./dist/types/exports/*.d.ts, legacy-module: ./dist/exports/*.js, import: ./dist/exports/*.mjs, require: ./dist/exports/*.cjs } } }source条件指向 TS 源码供 monorepo 内部构建Parcel直接消费避免重复打包./i18n/*对应intl/下各组件的语言包按语言代码取用如zh-CN.json./*通配每个功能目录都有独立入口sideEffects: false保证按需加载时未被使用的 Hook 可被摇树掉legacy-moduletargets配置中按chrome 79 / firefox 85 / safari 13编译的旧版 ESM 产物用于更保守的浏览器基线。构建入口统一来自exports/**/*.ts见targets.exports-module/targets.exports-main配置这意味着阅读exports/目录下的 97 个 TS 文件即可掌握包的全部公共 API 边界。六、验证路径测试与 Story 组织方式React Aria 的每个功能域都有对应的测试与故事文件形成“实现—测试—演示”三位一体的结构可作为深入阅读入口行为单测packages/react-aria/test/button/useButton.test.jstest/下按src/同构组织interactions、overlays、table、dnd等目录齐全仓库根的 jest.config.js 与 jest.ssr.config.js 分别支撑常规与 SSR 场景的测试。交互演示packages/react-aria/stories/button/useButton.stories.tsxstories/下 56 个故事文件覆盖主要组件配合 monorepo 的 examples/rsp-webpack-4 等示例工程可以在真实应用中体验 Hook 行为。七、小结什么时候用 React Aria怎么用基于本文梳理的仓库证据React Aria 的适用边界很清晰适用你在从零搭建自己的设计系统或组件库需要把 WAI-ARIA 行为、跨输入设备交互、国际化等“脏活”一次性解决同时保留完全自主的 DOM 与样式控制权不适用如果你只需要一套开箱即用的组件外观应使用同仓库的 React SpectrumAdobe Spectrum 设计系统实现或 react-aria-components 这类组合组件包使用模式useXxx(props, ref)传入行为与回调 props展开返回的xxxProps到自渲染的元素上需要状态时搭配 react-stately 的useXxxState应用根部用I18nProvider注入 BCP47 locale 即可获得 RTL 方向与本地化格式环境前提React16.8至19.0.0-rc.1以peerDependencies为准包版本3.52.1react-stately与之版本锁定3.50.0。至此README 中的四条特性声明——可访问、自适应、国际化、完全可定制——都可以在src/、intl/、exports/与package.json中找到对应的实现证据而 rfcs/2019-v3-architecture.md 则提供了三层架构的完整设计动机是继续深入的最佳入口。【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进