news 2026/9/14 22:57:49

react-spectrum S2 迁移实战:codemod 运行之后,如何完成剩余的手动修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
react-spectrum S2 迁移实战:codemod 运行之后,如何完成剩余的手动修复

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.tsTableView/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 依赖两张静态映射表做自动替换:

  • 图标映射表:例如AlertAlertTriangleAlertCircleAlertDiamondAudioMusicNoteAtMentionBookmarkSmallOutlineBookmark
  • 插图映射表。

映射表命中的导入会被自动改写;未命中的则按上文的规则留下TODO(S2-upgrade)注释。处理方式是:在@react-spectrum/s2的图标/插图集合中,按语义(名称、视觉形状、用途)挑选最接近的替代项,手动替换导入名,并删除 TODO 注释。由于图标命名体系在 S2 中发生了变化(如123TextNumbersBeakerBetaApp),建议对照 S2 图标索引逐个确认,而不是仅凭直觉猜测。

布局组件:Flex、Grid、View、Well 的 div 化

文档明确指出:FlexGridViewWell不属于 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_styleTODO(S2-upgrade): check this UNSAFE_classNameTODO(S2-upgrade): update this style prop注释;遇到 spread 展开属性时还会标记check this spread for style props。因此处理步骤是:

  1. 全局搜索UNSAFE_styleUNSAFE_className与上述 TODO 注释;
  2. 将内联样式对象/类名映射为style({...})调用,放入组件的className(或 S2 组件的styles属性);
  3. 对照 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';断点:basedefaultSsmMmdLlg。尺寸类属性(widthpaddingmargingap等)则需要按size-*/static-size-*令牌表换算为像素数字,如size-20016size-50040size-100080

Dialogs:关闭逻辑在 Dialog / DialogTrigger / DialogContainer 之间的重排

文档提示:DialogContaineruseDialogContainer在 S2 中仍然存在,但 dismiss(关闭)逻辑可能需要在DialogDialogTriggerDialogContainer三者之间移动。

从 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
  • 删除DialogTriggertargetRefclose函数在 S2 中属于Dialog侧的 render prop。

对于useDialogContainer的命令式打开场景,需确认返回的open回调签名是否仍然匹配;若 codemod 留下了 TODO 标记,按上述三个角色的分工手动调整即可。

Collections:Item 按父组件改名,并注意 id 要求

Item在 codemod 后仍然存在时(即父组件结构无法被自动识别,或落在映射范围之外),需要按父组件手动改名。迁移文档给出的映射表如下:

Parent componentv3 childS2 child
Menu / ActionMenuItemMenuItem
PickerItemPickerItem
ComboBoxItemComboBoxItem
TabsItemTab / TabPanel
TagGroupItemTag
BreadcrumbsItemBreadcrumb

这一点在 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.,这正是需要人工介入的信号。

文档还强调了两条实操要求:

  • 映射数组时保留 Reactkey,但要确保集合数据项在 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)、行项缺少idyou'll need to add an id prop to the Row)、嵌套ColumnNested Column components are not supported yet)、以及需要手动指定isRowHeader的列。对应地,table.test.ts 与 listview.test.ts 固化了这些转换行为的预期输出,可用于验证自己的修改是否与 codemod 语义一致。

Toast 迁移:导入路径、共享容器与队列调用

文档对 Toast 给出了六条具体操作,这里完整继承并结合仓库证据展开:

  1. ToastContainerToastQueue的导入从@react-spectrum/toast改为@react-spectrum/s2
  2. 保持一个共享的ToastContainer挂载在应用根部或测试 harness 附近,然后把所有队列调用更新为 S2 导入路径;
  3. S2 支持ToastQueue.neutralToastQueue.positiveToastQueue.negativeToastQueue.info四种类型;
  4. 导入迁移后,重新检查timeoutactionLabelonActionshouldCloseOnActiononClose等选项在 S2 下的行为;
  5. 队列方法仍然返回 close 函数——如果现有交互依赖编程式关闭(例如点击后立即消失),务必保留这段逻辑;
  6. 移动导入后,搜索所有ToastContainer挂载点和所有ToastQueue调用点——共享应用根部、次级入口点(secondary entrypoints)和测试 harness 是极易遗漏的位置。

第 6 条与仓库中 迁移前检查清单 的建议互相印证:该清单要求在迁移前就"找到所有入口点(包括独立页面、备用渲染根、内嵌子应用、仅测试用渲染目标)",并"定位共享测试包装器、toast 配置",同时把ToastContainerToastQueueDialogContaineruseDialogContainerUNSAFE_style等列为 codemod 之后的常见后续处理项。也就是说,多入口项目(例如仓库中 examples 目录下的多个独立应用)在迁移时应先枚举入口,再逐一核对 Toast 挂载与调用,避免某个子应用残留旧导入路径。

收尾自检清单

完成上述各类修复后,建议按 codemod 自身的 "Next steps" 口径做一遍自检:

  1. 确认@react-spectrum/s2已安装,且打包器支持 style macro(Parcel v2.12.0+ 原生支持;Vite、webpack、Next.js、Rollup、ESBuild 等需通过unplugin-parcel-macros类插件接入,且保证 macro 插件在其他插件之前运行);
  2. 若需要,在入口组件添加import '@react-spectrum/s2/page.css';(与 v3 不同,S2 不再需要 Provider);
  3. 全局搜索TODO(S2-upgrade),确认标记清零;
  4. 全局搜索@adobe/react-spectrum@react-spectrum/*(非 s2)、@spectrum-icons/*的残留导入;
  5. 运行项目的 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 22:49:38

Archon 提示 worktree 属于另一个 clone 怎么排查?

Archon 提示 worktree 属于另一个 clone 怎么排查&#xff1f; 【免费下载链接】Archon The first open-source harness builder for AI coding. Make AI coding deterministic and repeatable. 项目地址: https://gitcode.com/GitHub_Trending/archon3/Archon 当你在某…

作者头像 李华
网站建设 2026/9/14 22:49:07

运营稳定小程序卖货平台搭建哪家好?烘焙门店先看预售和自提流程。

烘焙门店做小程序卖货&#xff0c;和普通商品商城不完全一样。当天现烤、节日礼盒、生日蛋糕和到店自提都有明确时间要求&#xff0c;库存也会随着生产计划变化。顾客如果下单后无法确认取货时间&#xff0c;店员如果看不清预售订单和备注&#xff0c;再漂亮的页面也会给门店增…

作者头像 李华
网站建设 2026/9/14 22:49:04

SpringBoot+Vue图书管理系统:从数据库设计到前后端联调的完整实战指南

如果你点进来&#xff0c;大概率正在为毕业设计或课程设计发愁。SpringBootVue的图书管理系统&#xff0c;确实是经典中的经典&#xff0c;但经典也意味着你很容易撞车。真正拉开差距的&#xff0c;不是“你做了个图书管理系统”&#xff0c;而是“你做的图书管理系统能不能跑通…

作者头像 李华
网站建设 2026/9/14 22:48:48

国微CMS源码解析:PHP站群系统架构与二次开发指南

简介&#xff1a;基于PHP的国微CMS部队门户站群系统源码&#xff0c;是一套面向部队单位网站建设的内容管理解决方案&#xff0c;适用于需要构建多级子站点、统一维护信息门户的PHP开发人员及部队信息化技术支持者。该系统围绕多站点管理、用户权限控制、模块化设计、模板引擎与…

作者头像 李华