活着回来讲这件事
为什么在2024年使用TypeScript?现在是2024年,迁移至TS现在成了一个不错的长期选择。根据Stack Overflow的调查,TS的上升趋势明显,排名上升了两个名次,增长了8%——这个增长在榜单中是最显著的——而JavaScript略有下降,这一点值得注意。
2021年和2024年Stack Overflow最流行的编程语言。来源: https://survey.stackoverflow.co/2021#technology-most-popular-technologies。以及:https://survey.stackoverflow.co/2024/technology#most-popular-technologies-language。
除了流行趋势之外,我个人还发现开发者们在过去的几年里更愿意尝试类型安全的语言。对在JS中引入类型不再有恐惧,TS已经足够稳定和流行,如果你还没试过,可以试试看。相信我,你将会喜欢在编译时就能捕捉到错误,而不是等到单元测试,甚至更糟糕的是用户发现了错误。
现在的状况,我迁移的代码库还算不错。这是一个使用 yarn workspaces 设置的 Create React App 单代码库,并使用 CRACO 进行配置,测试运行正常,模块配置得当,代码检查也已做好。没有什么特别突出的地方,也没有什么特别需要抱怨的地方。等我们转向 Vite 时,那是另一天的事了。
在不太好的一面,该项目依赖 PropTypes 进行类型检查,这在你打算迁移到 React 18.3 或更高版本时需要解决。这暂时还不是问题,但 TypeScript 可以轻松解决这个问题,提供了一个简单的替代方案。这个问题留到以后再解决吧。
与此同时,
让我们跳进去吧!
移民方案管理单仓库中的代码库,包含整个设计系统和几个相关的包,可能相当有挑战。
配置 TypeScript 并不是一个难题,配置 Babel(或你最喜欢的编译器)、更新代码检查器或更新 Jest 配置也不是难事;正如俗话所说:
“巴西的一只蝴蝶扇动翅膀,也许会在德克萨斯引发龙卷。”
意思是你调整了 Babel 配置,规范检查器突然显示有错误。你修正了规范检查器的错误,测试却开始失败。你修正了测试,规范检查器又显示有错误。换句话说,这是一场不断修正问题的追求,要么是代码先出问题,要么是你的精神先崩溃,先到者先破。换句话说,要么是代码先出问题,要么是你的心灵先崩溃。
在这篇教程里,我会教你如何轻松修理代码中的问题。我花了大量的时间调试和理解这些问题,最终找到了一个解决方案,希望能让你能够轻松搞定。
我的个人建议是,每次你做个小改动时,进行 commit
提交,并继续前进。当你遇到问题需要 checkout
之前的某个项目阶段时,你的未来版本会感激现在的你。
当将 Create React App (CRA) 单一仓库迁移到 TypeScript 时,尤其是在使用 CRACO(以覆盖 CRA 的配置)时,确保只安装必要的东西以避免不必要的复杂性是很重要的。在我们解释这些依赖项的作用之前,先来看看我们需要哪些依赖项。
核心的 TypeScript :
TypeScript
(一种编程语言)
TypeScript 类型定义
-
@types/react
-
@types/react-dom
-
@types/react-router-dom
-
@types/node
@types/jest
(测试用的)
巴别塔(汇总):
@babel/preset-typescript
(用于将 TypeScript 语法转换为 Babel 可以处理的形式)
ESLint (用来校验 TypeScript)
@typescript-eslint/parser
(用于解析 TS 文件)@typescript-eslint/eslint-plugin
(为 TS 代码设定 lint 规则)
当你迁移到TS时,首先需要知道的是,除了长长的依赖列表之外,你还需要一些叫做“类型定义”的东西。你可以通过它们的.d.ts
文件扩展名来识别它们,它们让IDE能够帮助你识别正确的签名。
Intl.NumberFormat 的示例定义
这来自于 lib.es2020.intl.d.ts。
不言而喻,并非所有项目都有这些定义,这时DefinitelyTyped就发挥作用了。Definitely Typed 是一个项目,提供那些缺少类型定义的 NPM 包则的类型定义中央仓库。
所以,除了安装 TS 之外,你还需要安装其他依赖项的类型声明,以及各种插件和预设配置,以便让 Webpack、Babel 和 Eslint 能正常运行。
咱们按类别来看看这些依赖项,
- 安装TS及其相关类型依赖。确保您的TS版本与typescript-eslint所支持的版本一致,这在后续步骤中会用到。
yarn add -D typescript @craco/types @types/node @types/react @types/react-dom @types/react-router-dom @types/jest // 添加必要的依赖,包括TypeScript及其相关库和测试框架
2. 安装其他所需的 TypeScript 相关工具,主要是为了你的编译器、代码规范检查器、测试运行器等等。因为我用了 Babel 和 ESLint,我需要:
使用 Yarn 安装依赖:`yarn add -D @babel/preset-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser`
在项目中安装 TypeScript 相关的 Babel 预设和 ESLint 插件和解析器
3. 最后,为你正在使用的其他库(例如 Lodash 或 UUID)安装所需的类型定义:
运行以下命令来安装依赖项:yarn add -D @types/lodash @types/uuid
在你的项目根目录下创建一个 global.d.ts
文件。这个文件能避免 TS 在你导入非 TS 代码时出现错误信息,通过定义如何处理非 TS 代码的导入方式。
首先,你需要为你的样式添加样式定义,比如:
declare module '*.scss' {
const scssContent: Record<string, string>;
export default scssContent;
}
declare module '*.css' {
const cssContent: Record<string, string>;
export default cssContent;
}
随着时间的推移,这个文件会变得越来越大。例如对于 png
图像,你可以这样做:
// 模块声明 "*.png" 模块,这里的 const value: any; 和 export default value; 表示定义一个任意类型的默认导出值。
模块声明 "*.png" {
// 定义一个任意类型的默认导出值。
}
只要不是 .ts
的扩展名,都需要在这个文件里声明一下。
在你的单仓库中,你需要设置编译器来处理TS文件,比如在单仓库中。如果你使用的是CRACO,那将是你的craco.config.js
文件;如果不是,请修改你的编译设置,比如编辑babel.config.js
文件。
- 将 Babel 更新为包含 TS 配置文件:
module.exports = {
babel: {
cache: false,
presets: [
'@babel/preset-react',
'@babel/preset-typescript', // <-- 请在此添加这一行(为了添加TypeScript支持)
], // 预设(preset)
plugins: [
'@babel/plugin-proposal-export-default-from',
],
},
... // 等等
}
有些项目在这个文件中没有使用 babel
键,而是使用单独的 babel.config.json
。如果是这种情况,处理方式一样,你还是需要在相应的预设中添加 @babel/preset-typescript
。
2. 添加 .ts
和 .tsx
到 webpack
配置中的 resolve.extensions
数组,并在 module
部分加入 ts-loader
转换。可以参考如下配置:
+ test: /\.tsx?$/,
+ use: ['ts-loader'], // 可以根据需要添加额外的 loader 选项
+ exclude: /node_modules/
请根据项目需求调整 ts-loader
的选项。
webpack: {
configure: (webpackConfig, {
env,
paths
}) => {
return {
...webpackConfig,
resolve: {
...(webpackConfig.resolve || {}),
extensions: [
...(webpackConfig.resolve.extensions || []),
'.js',
'.jsx',
'.ts.', // <-- 添加这两行
'.tsx.', // <-- 添加这两行
],
},
},
};
};
添加这些文件将会告诉 webpack
如何处理它们。这些文件不是普通的纯 JS 文件,因此需要将它们转换成 Babel 可以理解的形式。
如果你之前有一个 jsconfig.json
,要和它说再见了。在使用 TS 时,tsconfig.json
是必需的文件。
甚至你可能根本没有 jsconfig.json
文件,这也没问题:这个文件并不总是必需,这也没问题。但在使用 TypeScript 时,tsconfig.json
是主要配置文件,我们确实需要它。每次你在项目中使用 typescript
作为依赖时,你肯定会有一个 tsconfig
文件。
通常,你会在整个项目中有一个全局的 TSconfig 文件,然后每个包中可能还有一些额外的配置来扩展全局的配置。
所以,在最高层,我们有这样:
而是这样:
//jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"moduleResolution": "node",
},
"exclude": [
"node_modules"
]
}
你将有
「// tsconfig.json: 用于配置TypeScript编译器的配置文件*/
{
"compilerOptions": {
"lib": [
"esnext",
"dom",
"dom.iterable"
],
"allowJs": true, // 允许JavaScript文件作为TypeScript代码编译
"checkJs": false,
"jsx": "react", // 将JSX语法编译为React组件
"baseUrl": ".",
"moduleResolution": "node",
"target": "ES6",
"module": "ES6",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./build" // 输出编译后的文件目录
},
"include": [
"./packages/**/*.ts",
"./packages/**/*.tsx",
"./packages/**/*.d.ts",
"./packages/**/*.js",
"./packages/**/*.jsx",
"./global.d.ts",
],
"exclude": [
"node_modules"
],
}
你会发现,这些设置允许在你的新 TS 文件中插入 JavaScript 代码。你也得把你那些旧的 js 文件加到 include
选项里。
同之前一样,对于每个包文件,如果你有一个 jsconfig
文件,请用新的替换它。确保你在扩展根设置的配置。
// tsconfig.json 位于某个包内
{
"extends": "../../tsconfig.json",
// 其他相关配置
}
在这个 tsconfig.json 文件中,`extends` 属性指向了上级的 tsconfig.json 文件。
步骤六:更新 ESLint 配置:
ESLint 默认解析器是 @babel/eslint-parser
。此解析器不支持不标准的 EcmaScript 语法,换句话说,它不支持 TypeScript。所以我们需要换成支持 TypeScript 的解析器,比如 @typescript-eslint/parser
。
所以,在你的 eslint
配置文件中更新解析器(parser)、扩展和插件等。
{
"parser": "@typescript-eslint/parser", // <-- 修改这一行
"extends": [
"airbnb",
"airbnb/hooks",
// 其他扩展
"plugin:@typescript-eslint/eslint-recommended", // <-- 添加这一行
"plugin:@typescript-eslint/recommended" // <-- 添加这一行
],
"plugins": [
"@babel",
"react",
"react-hooks",
// 其他插件
"@typescript-eslint" // <-- 添加这一行
],
... // 你的其他配置
}
更新了代码检查工具后,你可能会遇到由于新规则引起的新问题:如有此类问题,请修复它们并提交新的代码。
第7步:构建和检查代码此时,你应该已经有了一个可工作的单一代码库。检查测试能否运行,代码检查工具能否正确处理 .js
、.jsx
、.ts
和 .tsx
后缀的文件,并在出现问题时,请进行调试。
另一个重要的步骤是构建
每个包。在构建阶段,你可能会遇到一些在开发过程中未曾遇到的问题。不要把这留到最后一刻。
如果你做到了这一步,一切都很顺利,可以给自己点个赞,干得不错!
我遇到的坑在这次迁移过程中,我学到了很多东西。不仅了解了TSConfiguration,还加深了对系统在更大环境中如何协同作用的理解。
让我头发稍微变白的一件事是 tsconfig
文件中的 path
和 baseUrl
设置。原来,现在虽然不再需要 baseUrl
,但你仍然可以使用它。
所以我一直在摆弄这两个选项,但是一直没搞清楚,我试着把paths
指向其他包,但一直没搞定。
问题是,baseUrl
指向了嵌套在 src
文件夹里的位置,而我写的包的 path
却像是在 root
下。这显然行不通。
所以呢,这不行。
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "src",
"paths": {
"*": ["../packageMain/src/*"], // 从src开始,实际上应该再往上两级目录
"somePackage/*": [
"../somePackage/*" // 同样,对于somePackage,也应从src开始再往上两级目录
]
}
}
}
所以,要么是我要移除 baseUrl
,要么将 paths
更新为上一级目录。
{
"extends": "扩展../../tsconfig.json",
"compilerOptions": "编译器选项", {
"baseUrl": "基本URL src",
"paths": "路径", {
"*": ["../../packageMain/src/*"],
"somePackage/*": [
"../../somePackage/*"
]
}
}
}
Note: There's an issue with the direct translation of the JSON structure, which requires balancing the brackets and colons properly. Here's a corrected version:
{
"extends": "扩展../../tsconfig.json",
"compilerOptions": {
"baseUrl": "基本URL src",
"paths": {
"*": ["../../packageMain/src/*"],
"somePackage/*": [
"../../somePackage/*"
]
}
}
}
那个小错误让我花了不少时间,比我预期的要多 😅哈哈
你真的需要在 TypeScript 中用到 Babel 吗?如果你因为其他原因还在使用 Babel(比如使用 JSX 插件、polyfills 或自定义转换),那么你需要 Babel 和 TypeScript 的集成。否则,单独使用 TypeScript(通过 tsc
或 Webpack 的 ts-loader
)也可以完成转换。
不过,CRA 默认使用 Babel,如果你在迁移项目过程中保留了 CRA 的 Babel 设置,你仍然需要 Babel 在流程中。
如果你仅用 TypeScript 进行类型检查,且不依赖自定义的 Babel 插件,你可以跳过 Babel,转而使用 ts-loader
与 Webpack 结合,以获得更接近原生的 TypeScript 开发体验。
迁移到TypeScript在使用CRACO的Create React App单仓库中看起来可能是一项艰巨的任务,但有了明确的计划后,每一步都会变得容易处理。
这个过程包括更新配置、安装依赖项和调整 ESLint 和 Babel 的设置。最终结果是代码库更加健壮和易于维护。你会很欣赏 TypeScript 提供的类型安全性和减少的运行时错误,尤其是在大型项目中。
如果你在途中遇到问题——无论是代码规范冲突、过时的依赖项,还是不熟悉的 TypeScript 错误——请记住一次只解决一个问题,经常进行提交,并且最关键的是,保持耐心并一步步来。
这些步骤是对未来项目可扩展性和稳定性的一种投资。相信我,一旦你经历了这一切之后,你一定会为自己的选择感到庆幸。
祝你编程快乐!