react-spectrum S2 迁移实战:codemod 运行之后,如何完成剩余的手动修复
【免费下载链接】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
本篇基于 react-spectrum 仓库内置的 S2 迁移手动修复参考文档,系统讲解s1-to-s2codemod 运行结束后需要人工介入的六类典型问题:图标与插图替换、Flex/Grid/View/Well布局组件移除、UNSAFE_style/UNSAFE_className迁移到 style macro、Dialog 关闭逻辑重排、集合组件中Item的改名,以及 Toast 的导入迁移。读完本文,你可以对照仓库中 codemod 的源码与测试,确认每一处TODO(S2-upgrade)标记应如何消除,并将 v3(S1)代码完整落到@react-spectrum/s2。
背景:codemod 的自动化边界与 TODO(S2-upgrade) 标记
react-spectrum 提供了一个名为 Upgrade Assistant 的 CLI 工具,用于把 React Spectrum v3(即 S1)组件升级到 Spectrum 2(S2)。根据 升级助手 README,其用法为:
npx @react-spectrum/codemods s1-to-s2支持的选项:
-c, --components <components>:逗号分隔的待升级组件列表(例如Button,TableView),不指定则升级全部已实现 codemod 的组件;--path <path>:执行 codemod 的目录,默认为当前目录;-d, --dry:只预览不写盘;--agent:非交互模式,跳过交互提示、包安装和 macro 配置,适用于 CI 或 Agent 场景(前提@react-spectrum/s2已安装且可解析)。
该工具基于 jscodeshift 实现,每个组件对应一个 transform(见 codemods/components 目录 下Button/transform.ts、TableView/transform.ts等文件)。
关键在于:codemod 无法覆盖所有场景。从 主 codemod 入口 可以看到,它在无法自动处理时会向代码注入注释标记,例如:
- 动态导入无法处理时:
TODO(S2-upgrade): check this dynamic import; - 图标在 S2 中没有对应项时:
TODO(S2-upgrade): A Spectrum 2 equivalent to '${name}' was not found. Please update this icon manually.; - 插图无对应项时:
TODO(S2-upgrade): A Spectrum 2 equivalent to '${name}' was not found. Please update this illustration manually.。
而 CLI 入口 在完成转换后输出的 "Next steps" 中,明确把「搜索TODO(S2-upgrade)并逐一解决剩余的手动迁移项」列为下一步。本文其余章节,就是逐类处理这些标记的方法。
图标与插图:在标记处手动挑选最近的 S2 替代
迁移参考文档给出的规则很直接:如果 codemod 在某个图标或插图导入旁边留下了TODO(S2-upgrade),请手动挑选最近的 S2 替代项。
之所以会产生这种标记,是因为 codemod 依赖两张静态映射表做自动替换:
- 图标映射表:例如
Alert→AlertTriangle、AlertCircle→AlertDiamond、Audio→MusicNote、At→Mention、BookmarkSmallOutline→Bookmark; - 插图映射表。
映射表命中的导入会被自动改写;未命中的则按上文的规则留下TODO(S2-upgrade)注释。处理方式是:在@react-spectrum/s2的图标/插图集合中,按语义(名称、视觉形状、用途)挑选最接近的替代项,手动替换导入名,并删除 TODO 注释。由于图标命名体系在 S2 中发生了变化(如123→TextNumbers、Beaker→BetaApp),建议对照 S2 图标索引逐个确认,而不是仅凭直觉猜测。
布局组件:Flex、Grid、View、Well 的 div 化
文档明确指出:Flex、Grid、View和Well不属于 S2,需要改为用 style macro 加样式的div元素。这与仓库中 UPGRADE.md 迁移指南 对 Flex/Grid/View 的说明一致("UpdateFlexto be adivand apply flex styles using the style macro" 等)。
style macro 的导入方式为 import attributes 语法:
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};Flex 示例
Before:
<Flex direction="column"> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </Flex>After:
import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; <div className={style({display: 'flex', flexDirection: 'column'})}> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </div>Grid 示例
Before:
<Grid justifyContent="center"> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </Grid>After:
import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; <div className={style({display: 'grid', justifyContent: 'center'})}> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </div>View 示例
View本身不承担布局职责,直接替换为div即可:
// Before <View> Content </View> // After <div> Content </div>Well 示例
Well带有一组内建外观(边框、内边距、字号等),需要把这些视觉属性用 style macro 显式表达出来:
// Before <Well> Content </Well> // After import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; <div className={style({ display: 'block', textAlign: 'start', padding: 16, minWidth: 160, marginTop: 4, borderWidth: 1, borderRadius: 'sm', borderStyle: 'solid', borderColor: 'transparent-black-75', font: 'body-sm' })}> Content </div>注意 macro 中取值与 CSS 的差异:padding: 16是像素数字、borderRadius: 'sm'是 S2 的设计令牌名、font: 'body-sm'是 S2 的字体档位。style macro 支持的 CSS 属性集合以仓库内的 style macro 规则说明 与 S2 样式文档为准。
UNSAFE_style 与 UNSAFE_className:迁移到 style macro
文档要求:尽可能把UNSAFE_style的用法迁移到 S2 style macro,UNSAFE_className同理;支持的 CSS 属性清单见 S2 styling 文档。
从源码看,codemod 在 styleProps 转换器 中处理这些属性:能自动转换的直接改写,不能的则注入TODO(S2-upgrade): check this UNSAFE_style、TODO(S2-upgrade): check this UNSAFE_className或TODO(S2-upgrade): update this style prop注释;遇到 spread 展开属性时还会标记check this spread for style props。因此处理步骤是:
- 全局搜索
UNSAFE_style、UNSAFE_className与上述 TODO 注释; - 将内联样式对象/类名映射为
style({...})调用,放入组件的className(或 S2 组件的styles属性); - 对照 UPGRADE.md 中的 Style props 章节完成 v3 令牌值到 S2 取值的换算。
UPGRADE.md 给出了完整的换算表,例如边框宽度:'none'→0、'thin'→1、'thick'→2、'thicker'→4、'thickest'→'[8px]';圆角:'xsmall'→'[1px]'、'small'→'sm'、'regular'→'default'、'medium'→'lg'、'large'→'xl';断点:base→default、S→sm、M→md、L→lg。尺寸类属性(width、padding、margin、gap等)则需要按size-*/static-size-*令牌表换算为像素数字,如size-200→16、size-500→40、size-1000→80。
Dialogs:关闭逻辑在 Dialog / DialogTrigger / DialogContainer 之间的重排
文档提示:DialogContainer和useDialogContainer在 S2 中仍然存在,但 dismiss(关闭)逻辑可能需要在Dialog、DialogTrigger、DialogContainer三者之间移动。
从 codemod 源码可以印证这个变化的处理方式。transforms.ts 中的 moveRenderPropsToChild 函数负责把 render props 从DialogTrigger的 children 移动为Dialog的子级:它能识别形如({close}) => <Dialog>...</Dialog>的箭头函数子节点,自动把close参数改为对象解构({close}),并移除Dialog上的onDismiss;当渲染函数结构无法识别时,则留下TODO(S2-upgrade): Could not automatically move the render props. You'll need to update this manually.,或标记update this dialog to move the close function inside。
结合 UPGRADE.md 的 Dialog/DialogTrigger 条目,手动处理的口径是:
- 将 render props 从
DialogTrigger的第二个子元素移到Dialog内部; - 删除
Dialog上的onDismiss,改用DialogTrigger上的onOpenChange,或DialogContainer上的onDismiss; - 删除
DialogTrigger的targetRef;close函数在 S2 中属于Dialog侧的 render prop。
对于useDialogContainer的命令式打开场景,需确认返回的open回调签名是否仍然匹配;若 codemod 留下了 TODO 标记,按上述三个角色的分工手动调整即可。
Collections:Item 按父组件改名,并注意 id 要求
当Item在 codemod 后仍然存在时(即父组件结构无法被自动识别,或落在映射范围之外),需要按父组件手动改名。迁移文档给出的映射表如下:
| Parent component | v3 child | S2 child |
|---|---|---|
| Menu / ActionMenu | Item | MenuItem |
| Picker | Item | PickerItem |
| ComboBox | Item | ComboBoxItem |
| Tabs | Item | Tab / TabPanel |
| TagGroup | Item | Tag |
| Breadcrumbs | Item | Breadcrumb |
这一点在 Item 转换器源码 中有直接对应:codemod 通过updateComponentWithinCollection依次尝试 Menu/ActionMenu/ContextualHelpTrigger →MenuItem、TagGroup →Tag、Breadcrumbs →Breadcrumb、Picker →PickerItem、ComboBox →ComboBoxItem、ListView →ListViewItem的改名,并把key转换为id(若渲染在array.map中则同时保留key以满足 React 要求);父集合无法识别时调用commentIfParentCollectionNotDetected留下TODO(S2-upgrade): Couldn't automatically detect what type of collection component this is rendered in.,这正是需要人工介入的信号。
文档还强调了两条实操要求:
- 映射数组时保留 React
key,但要确保集合数据项在 S2 期望的地方暴露id(S2 集合文档有详细说明); - Table 和 ListView 的迁移通常需要人工复查行头(row headers)、嵌套列(nested columns)和显式 item id。从源码看这并非空话:TableView 转换器 会针对以下情况留下 TODO 标记——找不到
TableHeader导致无法推导columnsprop(Could not find TableHeader within Table to retrieve columns prop)、行项缺少id(you'll need to add an id prop to the Row)、嵌套Column(Nested Column components are not supported yet)、以及需要手动指定isRowHeader的列。对应地,table.test.ts 与 listview.test.ts 固化了这些转换行为的预期输出,可用于验证自己的修改是否与 codemod 语义一致。
Toast 迁移:导入路径、共享容器与队列调用
文档对 Toast 给出了六条具体操作,这里完整继承并结合仓库证据展开:
- 把
ToastContainer和ToastQueue的导入从@react-spectrum/toast改为@react-spectrum/s2; - 保持一个共享的
ToastContainer挂载在应用根部或测试 harness 附近,然后把所有队列调用更新为 S2 导入路径; - S2 支持
ToastQueue.neutral、ToastQueue.positive、ToastQueue.negative与ToastQueue.info四种类型; - 导入迁移后,重新检查
timeout、actionLabel、onAction、shouldCloseOnAction、onClose等选项在 S2 下的行为; - 队列方法仍然返回 close 函数——如果现有交互依赖编程式关闭(例如点击后立即消失),务必保留这段逻辑;
- 移动导入后,搜索所有
ToastContainer挂载点和所有ToastQueue调用点——共享应用根部、次级入口点(secondary entrypoints)和测试 harness 是极易遗漏的位置。
第 6 条与仓库中 迁移前检查清单 的建议互相印证:该清单要求在迁移前就"找到所有入口点(包括独立页面、备用渲染根、内嵌子应用、仅测试用渲染目标)",并"定位共享测试包装器、toast 配置",同时把ToastContainer、ToastQueue、DialogContainer、useDialogContainer、UNSAFE_style等列为 codemod 之后的常见后续处理项。也就是说,多入口项目(例如仓库中 examples 目录下的多个独立应用)在迁移时应先枚举入口,再逐一核对 Toast 挂载与调用,避免某个子应用残留旧导入路径。
收尾自检清单
完成上述各类修复后,建议按 codemod 自身的 "Next steps" 口径做一遍自检:
- 确认
@react-spectrum/s2已安装,且打包器支持 style macro(Parcel v2.12.0+ 原生支持;Vite、webpack、Next.js、Rollup、ESBuild 等需通过unplugin-parcel-macros类插件接入,且保证 macro 插件在其他插件之前运行); - 若需要,在入口组件添加
import '@react-spectrum/s2/page.css';(与 v3 不同,S2 不再需要 Provider); - 全局搜索
TODO(S2-upgrade),确认标记清零; - 全局搜索
@adobe/react-spectrum、@react-spectrum/*(非 s2)、@spectrum-icons/*的残留导入; - 运行项目的 linter / formatter(ESLint、Prettier)清理 codemod 产生的格式残留。注意 前置检查清单 给出的最低工具版本:TypeScript 5.3+(解析
with {type: 'macro'}语法)、Babel 7.27.0+ 或@babel/plugin-syntax-import-attributes、ESLint 9.14.0+ 配@typescript-eslint/parser、Prettier 3.1.1+,否则 import attributes 语法本身会导致解析或格式问题。
以上流程覆盖的正是 focused-manual-fixes.md 所定义的全部手动修复面。配合 UPGRADE.md 完整迁移指南(按组件列出 prop 级变更)与 codemods 测试快照(可对照 codemod 对各类组件的预期转换结果),即可完成从 S1 到 S2 的完整迁移。
【免费下载链接】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),仅供参考