关于 migrate-to-strict-api
指导 React Native 项目迁移到 Strict TypeScript API 的分步技能,涵盖前置条件检查、深层导入和类型调整,并通过类型检查及测试验证结果。
主要功能
- 01检查 React Native 前置条件
- 02启用 Strict API
- 03改写深层导入
- 04处理 TypeScript 错误
- 05运行类型检查和测试
适用场景
- 01将 React Native 项目迁移至 Strict API
- 02定位并修复迁移导致的类型错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ react-native-community/ skills --skill migrate-to-strict-api安装后在 Claude Code 中输入/migrate-to-strict-api确认已加载。
附带文件references/library-compatibility.md
详细介绍
迁移到 Strict TypeScript API
将 React Native 项目迁移到 Strict TypeScript API——自动生成的 TypeScript 类型,限定为 react-native 的根导出。Strict API 从 React Native 0.87 起默认启用,在 0.80–0.86 版本中为可选预览功能。
此迁移仅影响 TypeScript 分析,不会改变打包结果或运行时行为,因此可以安全地逐步应用,不存在发布风险。
调用方式
/migrate-to-strict-api
分步流程
请按顺序完成以下所有步骤。不要跳过任何步骤。
1. 验证前提条件
- 阅读
package.json并确认react-native版本为 >= 0.80。如果不是,请停止并告知用户先升级。记录版本是否为 >= 0.87——这决定第 3 步和第 5 步。 - 确认
tsconfig.json存在。 - 确认
node_modules/react-native/types_generated/index.d.ts存在。如果不存在,请告知用户先运行包管理器安装命令。
2. 更新依赖
有些库会提供原始 TypeScript 源码,并将其作为用户项目的一部分进行类型检查——最常见的是从设置文件导入的 Jest 模拟入口。在 Strict API 下,这些代码会产生位于 node_modules 下的错误,通常是 TS2307: Cannot find module 'react-native/Libraries/...',用户无法在自己的代码中修复。
- 确认已启用
skipLibCheck(@react-native/typescript-config会设置此选项)。不要禁用它。 - 在迁移过程中的任何时候,只要类型错误位于
node_modules/<package>下,就先更新该包,并检查其发布版本中是否包含 Strict API 兼容性修复。已知的修复版本列于 references/library-compatibility.md。 - 如果没有已修复的版本,请通过将导入的子路径重定向到无类型存根来本地修复,并告知用户向该库报告兼容性问题:
{
"compilerOptions": {
"paths": {
"some-library/jest/mock": ["./untyped-module.d.ts"]
}
}
}
// untyped-module.d.ts
declare const anyExport: unknown;
export default anyExport;
内联 @ts-ignore 在这种情况下不起作用——错误报告在库文件内部,而不是导入位置。
3. 在 tsconfig.json 中启用 Strict API
React Native >= 0.87——Strict API 默认启用:
- 如果
compilerOptions.customConditions中包含"react-native-legacy-deep-imports",请移除该项(这是临时退出选项,未来版本将移除),并保留"react-native"。 - 否则,无需更改配置。
React Native 0.80–0.86——选择启用:
- 将
"customConditions": ["react-native", "react-native-strict-api"]添加到compilerOptions。两项都必须添加:customConditions会替换(而不是合并)扩展配置中的值,因此省略"react-native"会移除@react-native/typescript-config设置的标准条件,并破坏依赖该条件的包的模块解析。项目必须使用"moduleResolution": "bundler"(或"node16"/"nodenext"),自定义条件才会生效。
4. 修复深层导入
4a. 首先运行 ESLint 自动修复
@react-native/eslint-plugin 提供了 no-deep-imports 规则及代码自动修复器,可将已知的深层导入(默认导入和类型导入)改写为根导入。如果已安装该插件——它随 @react-native/eslint-config 一起提供——请在项目的 ESLint 配置中启用此规则:
{"@react-native/no-deep-imports": "error"}
然后使用 --fix 对项目源代码运行 ESLint。建议之后继续启用此规则,以防止问题再次出现。
4b. 手动处理其余部分
阅读 node_modules/react-native/types_generated/index.d.ts。此文件是 Strict API 中所有可用导出的事实来源。每一行都将内部源路径映射到导出名称:
export { default as Alert } from "./Libraries/Alert/Alert";
export type { AlertButton } from "./Libraries/Alert/Alert";
解析此文件,建立两个映射:
- 值导出:内部路径 + 原始名称 → 导出名称(例如
./Libraries/Alert/Alertdefault →Alert) - 类型导出:内部路径 → 导出类型名称列表(例如
./Libraries/Alert/Alert→AlertType、AlertButton,……)
在所有源文件(.ts、.tsx、.js、.jsx)中搜索从 react-native/Libraries/... 或 react-native/src/... 子路径导入的剩余内容,并使用映射逐一改写:
| 深层导入模式 | 替换内容 |
|---|---|
import Foo from 'react-native/Libraries/.../Foo' | import {Foo} from 'react-native' |
import type {FooProps} from 'react-native/Libraries/.../Foo' | import type {FooProps} from 'react-native' |
import {named} from 'react-native/Libraries/.../Foo' | import {named} from 'react-native' |
如果文件中已有 react-native 根导入,请将其合并,而不是添加重复导入。
Codegen 导入改为使用 CodegenTypes 命名空间:
// Before
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
import type {Int32, WithDefault} from 'react-native/Libraries/Types/CodegenTypes';
// After
import {codegenNativeComponent, codegenNativeCommands} from 'react-native';
import type {CodegenTypes} from 'react-native';
// Usage changes: Int32 → CodegenTypes.Int32, WithDefault<...> → CodegenTypes.WithDefault<...>
InitializeCore 是没有根导出的副作用入口点——请将其替换为专用子路径(通常可在 Jest 设置文件和自定义入口点中找到):
- import 'react-native/Libraries/Core/InitializeCore';
+ import 'react-native/setup-env';
如果深层导入路径未出现在映射中,则说明它是有意设为私有的。请查找根 API 中是否有替代方案;如果没有适用于该用例的方案,请向用户说明,并指向反馈讨论。请注意,react-native/src/private/* 导入在 0.87 中也会在运行时被移除——必须删除这些用法,而不只是重新声明类型。
4c. 保留 Jest 字符串模拟不变
不要改写 jest.mock('react-native/Libraries/...') 或 jest.requireActual(...) 路径字符串。Jest 和 Metro 中的模块解析方式没有变化——这些字符串仍然有效,而且 TypeScript 不会检查它们。只有当测试文件将深层路径作为模块导入时,TypeScript 才会报错;如果根导出无法满足该用例,请保留导入,并显式抑制错误:
// @ts-expect-error - React Native internal, untyped under the Strict API
import NativeAppState from 'react-native/Libraries/AppState/NativeAppState';
5. 运行类型检查并修复剩余的类型错误
运行 npx tsc --noEmit。根据以下已知模式逐步修复错误:
组件 ref 和实例类型
核心组件现在被类型化为函数组件,因此组件名称不再适合作为实例类型(useRef<TextInput> 指的是组件函数,且 .focus() 等方法会显示为缺失)。这是最常见的阻碍。
- 在 0.87 及更高版本中,使用专用的
*Instance类型——每个支持 ref 的核心组件都导出一个此类类型(ViewInstance、TextInputInstance、ScrollViewInstance、FlatListInstance,……)。不支持 ref 的组件没有实例类型:InputAccessoryView、TouchableWithoutFeedback、experimental_LayoutConformance。 - 在 0.80–0.86 中,使用
React.ComponentRef<typeof Component>——它会解析为相同的实例类型,并且适用于 Strict API 和旧版类型。
// Before
const inputRef = useRef<TextInput>(null);
// After (0.87+) — preferred
const inputRef = useRef<TextInputInstance>(null);
// After (any version) — equivalent
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null);
凡是将组件名称用作实例类型的地方都要应用此修改:useRef / createRef 类型参数、包装组件的 ref 属性(ref?: React.Ref<ViewInstance>)、React.forwardRef<View, Props> 类型参数,以及保存实例的变量。
相关替换:
Animated.LegacyRef<View>已移除——请使用普通的*Instance类型;*Instance类型可与Animated.*组件变体透明配合。NativeMethods/NativeMethodsMixin已移除——请使用HostInstance或相应的*Instance类型。
*Static 类型已移除
现在,API 名称本身会同时作为值和类型导出:
// Before
import {Linking, LinkingStatic} from 'react-native';
function foo(linking: LinkingStatic) {}
// After
import {Linking} from 'react-native';
function foo(linking: Linking) {}
对于以前不存在同名类型的情况(InteractionManagerStatic、PixelRatioStatic、DevMenuStatic、KeyboardStatic、DevSettingsStatic、NativeModulesStatic),改用值的 typeof(例如 typeof PixelRatio)。
完整列表:AlertStatic、ActionSheetIOSStatic、ToastAndroidStatic、InteractionManagerStatic、UIManagerStatic、PlatformStatic、SectionListStatic、PixelRatioStatic、AppStateStatic、AccessibilityInfoStatic、ImageResizeModeStatic、BackHandlerStatic、DevMenuStatic、ClipboardStatic、PermissionsAndroidStatic、ShareStatic、DeviceEventEmitterStatic、LayoutAnimationStatic、KeyboardStatic、DevSettingsStatic、I18nManagerStatic、EasingStatic、PanResponderStatic、NativeModulesStatic、LogBoxStatic、PushNotificationIOSStatic、SettingsStatic、VibrationStatic
Animated 类型变更
Animated 节点不再是泛型类型,而是带有泛型 interpolate 方法的非泛型类型。请从 Animated.Value、Animated.ValueXY 等类型中移除类型参数。
已弃用的 *Properties 别名
旧版 *Properties 别名已移除。请改用匹配的 *Props 类型:ViewProperties → ViewProps、TextProperties → TextProps、ImageProperties → ImageProps 等;此外,ImagePropertiesSourceOptions 现在改为 ImageSourcePropType。
StyleSheet.absoluteFillObject
它不属于 Strict API。请替换为 StyleSheet.absoluteFill,两者等效,且在两种 API 模式下均可使用:
// Before
style={{...StyleSheet.absoluteFillObject, borderRadius: 12}}
// After
style={{...StyleSheet.absoluteFill, borderRadius: 12}}
useColorScheme() 和 'unspecified'
返回类型不再包含 'unspecified'——旧的手写类型对此值的定义不准确,且该值在运行时从未出现。请检查 'dark',并默认使用 'light':
// Before — handles a case that cannot occur
const theme = scheme === 'unspecified' ? 'light' : scheme;
// After
const theme = scheme === 'dark' ? 'dark' : 'light';
可选属性的类型为 type | undefined
现在,每个可选属性的类型都是 type | undefined。重新声明 React Native 属性的包装类型可能需要扩宽,以保持类型一致。
内部辅助类型
RecursiveArray、RegisteredStyle、Falsy、WithAnimatedArray、WithAnimatedObject 及其他仅供内部使用的辅助类型,不再能从 react-native 访问。请内联类型定义或寻找替代方案。
已移除遗留组件属性
仅存在于旧类型定义中的属性已移除,例如 Text 的 lineBreakMode、ScrollView 的 scrollWithoutAnimationTo,以及在 transform 数组之外声明的变换样式。请移除或替换这些用法。
6. 验证
- 再次运行
npx tsc --noEmit。确认不再存在类型错误。 - 如果项目有 Jest 测试套件,请运行测试。此次迁移不会影响模拟;如有新失败,请在继续之前进行调查。
请汇总所有变更:
- 修改的文件(数量)
- 重写的深层导入(数量)
- 已修复的类型错误(按类别)
- 需要提示用户的事项(没有根级等效项的私有 API、库兼容性问题)
中止迁移
如果无法完成迁移——例如库不兼容且无法解决、错误数量超出用户目前愿意处理的范围,或用户要求延期——项目可以暂时恢复使用旧版类型:
- React Native >= 0.87:在
compilerOptions中设置"customConditions": ["react-native", "react-native-legacy-deep-imports"](两个条目都必需,如步骤 3 所示)。此退出选项仅为临时措施,计划在未来版本中移除。 - React Native 0.80–0.86:从
customConditions中移除"react-native-strict-api",恢复其先前的值。
不要撤销在两种 API 模式下都有效的已完成工作——这些仍是后续尝试的进展:
- 将深层导入改写为根导入
- 使用
CodegenTypes命名空间(未启用 Strict API 时也会导出) react-native/setup-env- 将 Ref 类型写为
React.ComponentRef<typeof Component>
例外是 *Instance Ref 类型,它们仅在 Strict API 下存在——如果在步骤 5 后中止,请将其转换为等效的 React.ComponentRef<typeof Component> 形式。如果一开始就可能中止,建议始终采用这种形式。
在 0.87 及更高版本上中止时,请鼓励用户在退出选项讨论帖中分享遇到的阻碍。