mcpField
M
migrate-to-strict-api

migrate-to-strict-api

github.com/react-native-community开发28231 次安装12 周前更新
代码仓库

关于 migrate-to-strict-api

指导 React Native 项目迁移到 Strict TypeScript API 的分步技能,涵盖前置条件检查、深层导入和类型调整,并通过类型检查及测试验证结果。

主要功能

  1. 01检查 React Native 前置条件
  2. 02启用 Strict API
  3. 03改写深层导入
  4. 04处理 TypeScript 错误
  5. 05运行类型检查和测试

适用场景

  1. 01将 React Native 项目迁移至 Strict API
  2. 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/Alert default → 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 及更高版本上中止时,请鼓励用户在退出选项讨论帖中分享遇到的阻碍。

评论

支持 Markdown