向软件工程师征求意见时,原型响应就是“好吧,这取决于” ,无论问题看起来多么直接。 当被问到“我们应该为下一个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 directory
的package.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