为了账号安全,请及时绑定邮箱和手机立即绑定

精通在 Create React App 单仓库中迁移至 TypeScript 的方法:使用 CRACO 的实用指南

活着回来讲这件事

为什么在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

https://www.libhunt.com/index

除了流行趋势之外,我个人还发现开发者们在过去的几年里更愿意尝试类型安全的语言。对在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 能正常运行。

咱们按类别来看看这些依赖项,

  1. 安装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

步骤 2: 添加一个名为 global.d.ts 的文件

在你的项目根目录下创建一个 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 的扩展名,都需要在这个文件里声明一下。

步骤 #3:更新编译器工具和Webpack Settings

在你的单仓库中,你需要设置编译器来处理TS文件,比如在单仓库中。如果你使用的是CRACO,那将是你的craco.config.js文件;如果不是,请修改你的编译设置,比如编辑babel.config.js文件。

  1. 将 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.tsxwebpack 配置中的 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 可以理解的形式。

第4步, jsconfig.json 变为 tsconfig.json

如果你之前有一个 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 选项里。

步骤 5:将包里的 jsconfig.json 文件改为 tsconfig.json 文件

同之前一样,对于每个包文件,如果你有一个 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 后缀的文件,并在出现问题时,请进行调试。

另一个重要的步骤是构建每个包。在构建阶段,你可能会遇到一些在开发过程中未曾遇到的问题。不要把这留到最后一刻。

步骤#8:给自己一个赞

如果你做到了这一步,一切都很顺利,可以给自己点个赞,干得不错!

我遇到的坑

在这次迁移过程中,我学到了很多东西。不仅了解了TSConfiguration,还加深了对系统在更大环境中如何协同作用的理解。

让我头发稍微变白的一件事是 tsconfig 文件中的 pathbaseUrl 设置。原来,现在虽然不再需要 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 错误——请记住一次只解决一个问题,经常进行提交,并且最关键的是,保持耐心并一步步来。

这些步骤是对未来项目可扩展性和稳定性的一种投资。相信我,一旦你经历了这一切之后,你一定会为自己的选择感到庆幸。

祝你编程快乐!

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消