RN 版本升级指北

最近客户端打算将 react-native 版本升级到 0.64.1,此前是 0.59 版本。中间踩了不少坑,记录一下升级过程。

  1. 升级一下 react-native 和 react,由于项目使用了 Typescript,因此还要升级一下对应的 types 库

    npm i react-native@0.64.1 react@17.0.1 @types/react  @types/react-native
  2. 运行后报错: Module xxxx is not a registered callable module.

    根据 https://github.com/react-native-community/upgrade-support/issues/47 这个 issue 提到的需要配置 metro 中的 resolver 来跳过对 node_modules 中的 react-native 的编译。这让我想到有可能是由于我们用到一个私有包(以下简称 A 吧)。A 这个包里有 react-native 的依赖,并且声明到了 dependencies 里了。因此会存在两个版本的 react-native。

    考虑到这点,我对 A 中的 react-native 也进行了升级。发现即使是相同版本,依然会出现报错。因此打算先按 issue 提到的来进行配置。

    issue 中提到的 resolver 配置如下

    const blacklist = require('metro-config/src/defaults/blacklist');
    module.exports = {
      transformer: {
        getTransformOptions: async () => ({
          transform: {
            experimentalImportSupport: false,
            inlineRequires: false,
          },
        }),
      },
      resolver: {
        blacklistRE: blacklist([
          /node_modules\/.*\/node_modules\/react-native\/.*/,
        ])
      },
    };

    在 0.64.1 里并未找到 blacklist,我这里配置 resolver 为

    const exclusionList = require('metro-config/src/defaults/exclusionList');
    
    module.exports = {
      transformer: {
        getTransformOptions: async () => ({
          transform: {
            experimentalImportSupport: false,
            inlineRequires: false,
          },
        }),
      },
      resolver: {
        blockList: exclusionList([
          /node_modules\/.*\/node_modules\/react-native\/.*/,
        ]),
      },
    };

    这时候可以成功运行。

    更好的办法是,我们不在 A 中将 react-native 设置为 dependency,而仅需要最外层一个 react-native 即可。我们要做到这点,可以将 react-native 从 dependencies 中移除,改为 peerDependencies。同时为了方便开发,我们还应该将其声明到 devDependencies 中。如果做了这些,其实上述 metro 的配置就无需修改了。

  3. 运行后提示 hook 不能在函数式组件以外使用。我跟着查看了报错的组件,该组件由 A 提供,奇怪的是该组件确实是函数式组件,根据提示,怀疑可能是内外两个版本的 react 导致的,因此类似步骤一所示,将 react 依赖移出即可

  4. 加载页面时,提示有模块路径引用错误。我们项目中 babel 配置了自定义的 preset(以下简称 B)。B 中使用 babel-plugin-transform-imports 来按需加载 react-native 导出的模块。而由于 react-native 导出的模块路径变更,导致报错。我一开始在想 metro 是否会提供类似 tree-shaking 的处理,搜索后发现 metro 并不支持,见 https://github.com/facebook/metro/issues/227 。难道只能一个个修改路径了么?

    幸运的是,有这么个 pr https://github.com/facebook/metro/pull/362 ,通过 @babel/plugin-transform-modules-commonjs 将 esm 专程 commonjs 并且实现懒加载,并且还是默认为懒加载,这不就是我需要的么?因此简单处理将 babel-plugin-transform-imports 中关于 react-native 的部分全部删除即可

  5. 运行提示:A 包中的 TextPropTypes 等文件找不到。看到文档说到:

    Remove PropTypes: We’re removing propTypes from core components in order to reduce the app size impact of React Native core and to favor static type systems which check at compile time instead of runtime

    emm,为了减少应用体积已经将 PropTypes 移除了,并且倾向于使用静态类型系统。那就没什么好说了,只能将 PropTypes 相关的移除,后续再慢慢添加类型声明文件。

  6. 提示: Warning: ReactClass: You’re attempting to include a mixin that is either null or not an object.

    A 包中有部分旧的组件中用到 mixin,react 已经不推荐使用 mixin 啦,并且看到 mixin 的是 NativeMethods,目前没有场景会使用到,先简单移除掉 mixin,后续考虑重构。

  7. 报错:VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead. 新版本 rn 的 ScrollView 不能嵌套同方向的 VirtualizedList。

    最简单的处理方式是将 ScrollView 改为 FlatList,将原先 ScrollView 中的 children 改为 FlatList 的 ListHeaderComponent, 同时注意 ScrollView 与 FlatList 可能有些 api 不太一样,需要自行调整一下。

  8. 提示:Calling getNode() on the ref of an Animated component is no longer necessary。Animated 组件不需要通过 getNode 来获取节点了,直接用 ref 即可

  9. 提示:Animated.event now requires a second argument for options。即此前有默认值的 useNativeDriver,现在需要手动给值了。

  10. 报错:Invariant Violation: ViewPagerAndroid has been removed from React Native. It can now be installed and imported from ‘@react-native-community/viewpager’ instead of ‘react-native’. 如果有使用到或者是第三方库有使用到 ViewPagerAndroid 的,如 react-native-scrollable-tab-view,记得升级一下,使用 @react-native-community/viewpager。

以上为升级 RN 到 0.64.1 踩过的坑,仅供参考。