将50K SLOC Flow + React Native应用程序迁移到TypeScript

Sergey PesterevUnsplash上的照片

向软件工程师征求意见时,原型响应就是“好吧,这取决于” ,无论问题看起来多么直接。 当被问到“我们应该为下一个React Native项目选择TypeScript还是Flow?” 但是,答案仅取决于一个变量: 您是否在Facebook工作

考虑我们如何到达这里很有趣。 项目团队在大约三年前为我们的新React Native应用程序评估了Flow vs TypeScript。 当时,TypeScript不能很好地支持React,不允许逐步加入,没有Babel支持,VSCode并不是提供市场上最好的JavaScript开发经验的编辑器。

这些因素今天都不是正确的。 但是,为什么要迁移呢?

TypeScript具有许多可提供的价值,可以使交换机具有很高的吸引力:

  • 更多“未来证明”
  • 更高的OSS采用率和更好的RN +库支持
  • 很棒的开发人员经验(VSCode)
  • 与其他部门项目的一致性
  • 容易租用

所以,我们奋进! 但是怎么样了?

之前:50K SLOC,84%的流量覆盖率

$ npx sloc app
---------- Result ------------
Physical :  57903
Source :  50859
Number of files read :  709
----------------------------
$ npx flow-coverage-report  -i 'app/**/*.js'
percent: 84 %
$ time flow check
flow 3.64s user 2.31s system 32% cpu 18.458 total

入门

我们采取的第一步是更新JS工具配置。 对于我们的项目,该过程相对简单,主要是通过使用react-native cli直接从新实例化的TypeScript模板项目中直接复制配置来进行指导。

漂亮和ESLint

{
    "parser" : "typescript"
}

选择TypeScript时曾经不得不处理TSLint,但值得庆幸的是TSLint已被弃用 ,因此在2019年这是一个简单的选择-坚持使用ESLint!

从字面上看,没有必要进行任何更改(无论如何,为了我们的使用,其中包括插件和自定义规则!)

做完了 下一个。

除了奖金:删除Black Magic模块导入

您是否知道可以使用json文件中的name属性导入与package.json文件相关的JavaScript模块,而无需插件?

黑魔法! 但这是有用的黑魔法!

为了完成绝对路径导入(即, import Foo from 'app/ui/components/Foo')我们在项目中包括了/app directorypackage.json (以及其他几个,共4个package.json):

{ "name" : "app" }

从树中的其他文件夹导入时,这甚至可以工作(这意味着您可以从“ app”文件夹之外的app/components/Foo导入)。

好吧,TypeScript不喜欢这种黑魔法,无法弄清楚我们的导入:(。因此,我们删除了多余的package.json文件,而是选择使用babel。

巴别塔

借助Babel 7中对TypeScript的内置Babel支持,将我们的babel配置从Flow转换为TypeScript是一个简单的删除未使用插件的过程(例如@ babel / plugin-transform-flow-strip-types )。

这是我们最终的Babel.config.js,其完成的模块分辨率较低。

module .exports = {
    presets : [ 'module:metro-react-native-babel-preset' ],
    plugins : [
        [
            'module-resolver' ,
            {
                root : [ '.' ],
                alias : {
                    app : './app'
                },
            },
        ],
    ]
}

tsconfig.json

尽管运行React Native应用程序并非严格要求(Babel仍然会剥离/忽略TS语法),但使用TypeScript编译器(和VSCode工具)检测类型错误时,tsconfig是必需的。

值得庆幸的是,通过简单地从新的样例React Native项目中复制tsconfig,我们就获得了90%的解决方案。

但是,我们在项目中还包含一些.json文件,这些文件需要一些其他配置。

{
  "compilerOptions" : {
    "baseUrl" : "." ,
    "allowJs" : true ,
    "allowSyntheticDefaultImports" : true ,
    "esModuleInterop" : true ,
    "isolatedModules" : true ,
    "jsx" : "react-native" ,
    "lib" : [ "es6" , "dom" ],
    "moduleResolution" : "node" ,
    "noEmit" : true ,
    "strict" : true ,
    "target" : "esnext" ,
    "resolveJsonModule" : true , // Required for JSON files
    "skipLibCheck" : true
  },
  "exclude" : [
    "node_modules" ,
    "e2e" ,
    "**/*.json" , // Don't try and check JSON files
    "**/*.spec.ts" ,
  ]
}

将Flow迁移到TS语法,并将.js迁移到.ts / x

值得庆幸的是,开源社区已经完成了一些工作,以帮助从Flow转换为TypeScript语法,并且这些项目支持许多通用语言功能。

但是,我不能说这些项目往往维护得很好或成为主流。

有两种主要的解决方案可用,即流向打字稿babel-plugin-flow-to-typescript 。 我们首先尝试了“流程到打字稿”库,但由于它在包含函数的任何文件( WTF? )上崩溃而被抛弃了。

幸运的是,babel-plugin-flow-to-typescript为我们工作了。 几乎。

在编写与功能支持相关的错误时 ,我们不得不使用fork 。 我不知道这些工具和支持简单的旧功能有什么用,但是不管用什么,我想我应该心存感激!

将.js扩展名的JavaScript + Flow转换为.ts的TypeScript:

yarn add global @babel/cli @babel/core babel-plugin-flow-to-typescript
# Convert single file
npx babel script.js -o script.ts --plugins=babel-plugin-flow-to-typescript
# Convert all files and delete original JS after conversion
# Prereq: 'brew install parallel'
find app - type f -name '*.js' | parallel "npx babel {} -o {.}.ts && rm {}"
注意:由于主存储库中的错误,我们实际上使用了@ zxbodya / babel-plugin-flow-to-typescript。

接下来,重命名在顶部导入React的所有文件,以改用.tsx:

find app/components -name "*.ts" - exec sh -c 'git mv "$0" "${0%.ts}.tsx"' {} \;

健全性检查:该应用是否仍在运行?

在继续进行下一步之前,值得检查一下该项目是否实际运行。 因为Babel用于将TypeScript编译为JavaScript,并且Babel只是剥离了所有与TypeScript相关的语法,而不管您的项目有多少个类型错误应该“正常工作”。

就我们而言,不到半天就可以更新配置,重命名文件,迁移所有语法并重新获得运行的应用程序。

但是工作显然还没有结束。

修复数千个编译错误

在我们的案例中,工作从大约1800 TS编译错误开始。

修正此数量的错误并不是一个线性的过程,一次修正意味着您的错误会下降到1799年。有时,进行更改会减少数十个错误,使您感觉自己像凡人一样,有时会增加总体错误计数让您的信心动摇!

#1-解决隐含的任何问题

TypeScript会抱怨的第一件事是隐式的any。 主要是由于TS和Flow在类型推断方面的差异,因此必须立即添加其他注释。

警告: 不要简单地在TypeScript抱怨的地方简单地添加类型 。 而是问“为什么这东西呢?”

// Problem here
const reqs = get(props, 'requests' , [])
const inProgressIds = reqs.map( req => req.id)
const activeRequests = actions
    // Error shown here
   .filter( action => inProgressIds.includes(action.id))

而不是为action添加类型 如错误所示, reqs缺少类型,从而在下面产生错误。 根本原因可能是该功能中的另一个问题,甚至可能是一个完全不同的文件!

#2-更新React组件

转换中最不愉快,最“毛茸茸”的部分很容易迁移React代码。 在React代码中抛出的错误通常具有很长的错误链,有时表面上看起来完全与所需修复无关的细节。

我们的项目限制了从“智能”组件中使用React Native原语,而是选择在可重用的UI框架中创建我们自己的原语集。

/* @flow */
import Button as RNButton from 'react-native'
// Seal to avoid warning about inexact spread
type PropTypes = {|
    onPress: () => mixed,
    custom : boolean,
|}
// Trivialized component
const Button = ( props: PropTypes ) => <RNButton {...props } />

在Flow下,必须将传递给<Button>每个道具添加到PropTypes ,而我们宁愿采用<RNButton>所做的所有道具,然后添加我们自己的自定义道具。

// Import props available for every React Native component!  :D
import Button as RNButton, { ButtonProps } from 'react-native'
interface PropTypes extends ButtonProps {
    custom : boolean,
}
const Button = ( props: PropTypes ) => <RNButton {...props } />

更可取(准确)!

接口是有关TypeScript的最好部分之一!

#3-重构HOC

通过TypeScript获得Flow的好处之一是可以在函数体内使用泛型类型参数。 不确定一般情况下这是多么明智,但是在为高阶组件添加类型时,这是一个很棒的功能!

// TS allows the use of generics in the body of your HOC.  Yes!
const withAThing = < T > (config: Config <T>) => 
    (WrappedComponent: React.ComponentType<T>) => 
        (props: T) => <WrappedComponent {...props } />

但是,为了利用此功能,我们或多或少需要完全重写所有高阶组件的类型定义。 哎哟。

不用说我们更喜欢钩子!

之后:96%的TypeScript覆盖率

$ type -coverage -p tsconfig.json # ignored tests + storybook
68712 / 71471 96.13%
$ time tsc
tsc  29.82s user 1.76s system 166% cpu 18.955 total

覆盖率增加约12%。 执行时间大致相同。

到目前为止,最严重的类型覆盖差距存在于我们的Redux Saga代码中。 在Flow中也是如此,但是似乎改用TypeScript并没有太大改善。

摘要

如果您仍将Flow与React Native一起使用,切换从未如此简单!

相当于3名工程师花了10个完整工作日才能完成转换,包括回归测试,代码审查,重构和开发新的使用模式所花费的时间。

使用Google搜索错误消息可以轻松解决大多数错误,而需要一些时间,注意事项和经验来对修复程序充满信心。

尽管我们发现了一些错误(大多数与不正确的第三方API使用有关),但这对这项工作没有太大的好处。 对我们的React代码进行一些额外的介绍确实可以消除一些死代码。

总体而言,我们在社区支持和开发人员经验方面都实现了更好的基础,可以更好地进行未来的开发。

From: https://hackernoon.com/migrating-a-50k-sloc-flow-react-native-app-to-typescript-c91aj3ton

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值