立即领取|华润集团、宁德核电、东风岚图等 20+ 标杆企业数字化人才培养实践案例 了解详情
写点什么

从 0 到 1 开启一个全新的 TypeScript 项目

  • 2022-07-21
  • 本文字数:4942 字

    阅读完需:约 16 分钟

从0到1开启一个全新的 TypeScript 项目

文由极客时间整理自 FreeWheel 核心业务团队高级软件工程师陈芸在 QCon+ 案例研习社的演讲《TypeScript 在 FreeWheel 核心业务团队的项目实践(上)》。


作者|陈芸

编辑|贾亚宁


热衷前端技术的小伙伴都知道 TypeScript 这几年的需求呈现指数级增长的趋势,越来越多的开源项目开始使用 TypeScript 进行重构,出于对 TypeScript 究竟好不好,好在哪里的好奇,我们也对它进行了探索与尝试。


我本次的分享主要分为以下两个部分:首先探讨一下是否要引入 TypeScript,其次我们挑选了部门内比较典型的 TypeScript 项目,来带你尝试开启一个全新的 TypeScript 项目。

是否引入 TypeScript


第一部分我们先抛出一个疑问,我的项目是否应该引入 TypeScript?


想要引入一门新的语言肯定是有诉求的,如果原来的 JavaScript 对我们的开发来说非常完美,那大可不必做这样的尝试。既然 JavaScript 存在着问题,那我们就总结一下最大的痛点,看看 TypeScript 是否可以帮助来解决。


首先,找一找你平时经常遇到的前端 bug,很大一部分可能都是缺少“约束”导致的。缺少“约束”是什么意思呢?就是你制定了一个逻辑规则,但是并没有完整地描述这个规则,使得你获得的结果集合中总是会出现你期望之外的可能。我们先来看一个简单的例子:


减少线上 bug

这个 function 功能非常常见,就是从一个 list 中 filter 出某一项。看这三个结果,我期望这里的 item 包含匹配属性 id,且必须是 number 类型,这里的“必须包含以及类型限制”就是一种约束。


这个例子非常简单,但是我们在实际的项目中会有很多复杂的逻辑,经常会忘记当时写代码时思考清晰的约束逻辑,合作的小伙伴也不知道有这样的约束,就可能会触发 bug。那么这样的约束逻辑是不是可以显式地写在代码里,而不是只维护在作者的脑子里,这时我们就可以借助 TypeScript。当然有人会说我们可以借助测试来提前发现这样的 bug,但是对于大型项目而言,测试很难做到覆盖所有的逻辑。


这之后,我们还想提高开发效率。


我们在项目开发中常常会调用很多第三方的包,这些包怎么使用,我们往往需要去查看文档,还要注意版本是否一致,有时甚至需要去看源码,这是非常耗时的。不仅仅是第三方包,就是我们公司内部开发的 lib 库,在调用时也存在着同样的问题。尤其是项目团队中人员比较多的情况下,当我们需要互相调用对方开发的组件时,往往需要付出比较大的沟通成本。


提高开发效率

这个问题,TypeScript 也可以很好地帮助到我们,尤其是它的编辑器有友好的类型提示功能,还可以自动补齐代码,在提升开发效率的同时,还可以减少引用的出错。


既然 TypeScript 可以帮我们解决这些痛点,那我们就动手实践一下,看看是不是真如外界说的那么好,同时也看看它会带来哪些问题。

开启全新的 TypeScript 项目


首先,我们遇到了一个契机,公司要开启一个新的前端项目,这个项目是把原来系统中一个高度复杂的业务模块进行改版。


这个项目的规模,大概 15 万行的代码量,前端开发团队大概十几人。项目的特点是逻辑非常复杂、上线时间紧迫、测试可覆盖的面比较小。接下来我们从以下三个方面来介绍我们是如何开启一个全新的 TypeScript 项目的:项目配置和目录设计方案,以及一些常见问题的处理方案。


首先说一下我们的项目配置方案:大家都知道 TypeScript 被诟病的一个很大的问题就是它的 compile 耗时,对于一个大型项目而言,每一次改动需要等待多长时间才能生效将严重影响到前端的开发效率,所以选择什么样的编译方式是我们面临的第一个问题。


先来看一份数据对比:


数据对比

首先 ts-loader 是一个 webpack 上针对 TypeScript 的加载器,ts-loader 内部是调用 TypeScript 的官方编译器 tsc 实现的,它整个编译过程包含类型检查和语言转换,我们知道这里的类型检查是非常耗时的,常见的一种解决方式是把 option 中的 transpileOnly 设置为 true, 这样就只做语言转换而不进行类型检查,相当于只是把类型剔除掉,然后我们再通过别的辅助方式在一个单独的进程里做类型检查,可以看到 compile 的耗时减少了。


那么可不可以更快呢?大家都知道 esbuild 是一个基于 Go 的打包工具,它的运行效率是非常高的,所以我们用 esbuild-loader 替换了 ts-loader,它的运行过程也是剔除类型进行语言转化,同样我们可以用别的插件来单独解决类型检查的问题,可以看到耗时又显著减少了。


当文件越来越多,这个差距也会越来越大。


进程示意图

从上面这个图中可以看到,我们项目使用了 webpack5+esbuild 来进行 bundle。在另一个进程中使用插件来进行类型检查,由于是另起进程,所以它不会阻塞主进程的 bundle 过程。


使用了插件 fork-ts-checker-webpack-plugin 来进行类型检查


使用了 esbuild-loader 作为 TypeScript 文件的加载器


对应到实际 webpack 配置文件,我们使用了 esbuild-loader 作为 TypeScript 文件的加载器,使用了插件 fork-ts-checker-webpack-plugin 来进行类型检查。其他的 webpack 配置和 JavaScript 项目是一致。


fork-ts-checker-webpack-plugin 的效果 2-1


fork-ts-checker-webpack-plugin 的效果 2-2

使用 fork-ts-checker-webpack-plugin 的效果如上图所示,在 Terminal 中可以查看 compile error 的详细信息,如果编译通过,则显示 no issue found。但如果只是这样其实还是不够的,因为我们完全可以忽略类型检查报的错,继续提交代码,那 TypeScript 也就没有意义了,怎么约束呢?


package.json

我们的做法是把 tsc 作为 lint 的一部分,无论是本地提交代码,还是线上打包,lint 不过时无法完成的,这就起到了强制的作用。从这个命令中我们还可以看到,除了 tsc 以外,我们还使用了 eslint 来对 TypeScript 做代码检查。


2019 年 1 月,TypeScirpt 官方决定全面采用 ESLint 作为代码检查的工具,并创建了一个新项目 typescript-eslint。


有人会觉得,JavaScript 非常灵活,所以需要代码检查。而 TypeScript 已经能够在编译阶段检查出很多问题了,为什么还需要代码检查呢?因为有许多非类型问题是 tsc 所不关注的,比如代码风格方面可以用 eslint 来约束。


eslintrc - @typescript-eslint

上图是我们的 eslint 配置,具体每一条官方文档都有清楚的解释,这里就不逐条解说了,我就拿 no-empty-interface 为例,当我们代码里写了这样的 code:


代码演示

首先是空的 interface, 我们知道在实际代码中定义一个没有任何值的空对象是没有什么意义的,所以相应地也不应该出现这样的类型定义。然后是一个 interface 继承另一个类型后不做任何扩展,这样的写法相当于这两个类型就是完全相等的,也不应该出现这样的写法。


eslint 检查

诸如此类的问题 tsc 都是不会报错的,但这样的写法可能会给将来埋下隐患,所以我们通过 eslint 检查来规避一下。这里顺便提一下,有一些代码格式方面的约束比如缩进、空行、空格等之类的,我们并没有通过 eslint 来做,而是通过 prettier 来帮助完成。


接下来我们来介绍一下 TypeScript 项目最重要的 tsconfig 配置,下图是我们项目使用的配置方案:


tsconfig.json

这里我重点挑了几个参数:首先我们把 noEmit 设置成了 true,因为在我们项目中 tsc 只负责进行类型检查,并不真实输出 js 和.d.ts 文件。


paths

然后是 paths, 是用来 alias(取别名)的,配合 webpack 中的 alias,我们在 import 一些包的时候会用 alias 代替相对路径,那么在 TypeScript 类型检查的时候也需要知道这些别名才能找到对应的模块,完成检查。

include 和 exclude

最后是 include 和 exclude,include 指定某些路径下的文件被包含进来,exclude 排除一些路径。“include”的默认值是当前目录及其子目录下的所有 TypeScript 文件,“exclude”默认情况下会排除 node_modules、bower_components、jspm_packages 和目录,这里的 exclude 其实我们也可以省略。


files

此外还有两个我们项目中没有使用,但可能大家会用到的参数:首先是 files,如果想明确指定某几个 TypeScript 文件加入到 include 中,可以用“files”这个参数来添加。需要注意的是,通过“files”属性明确指定的文件总是会被包含在内,不受 exclude 的约束。


typeRoots

然后是 typeRoots,它默认值是 node_module 下的 @type,以及各个子路径下的 node_modules /@types。它的作用是:我们代码中 import 的一些第三方库,这些库的类型文件有全局声明,只有把他们添加进来,全局声明才会生效。需要注意,如果自定义 typeRoots,那么默认值就失效了,不要忘记手动把 node_modules/@type 也添加进去。


现在我们项目的框架已经搭好了,接下来介绍一下项目中的文件目录结构安排,通过项目实践,我们调整出的目录结构是好维护并有利于扩展的。下图展示了项目的文件目录结构,是一种我们比较推荐的 practic。


文件目录结构

除了最外层的项目配置文件外,首先,一个 component 下会有以 component 为前缀的四个文件,分别是 hook.ts、.css、.types.ts 以及 component 本身,类型定义相关的代码会被放到单独的.types.ts 文件中,这样做是为了使 component 仅仅只包含业务逻辑代码。


其次,有许多公共类型会被多次调用,这样的类型我们通常会放到 lib/types 下。当然,全局 declare 的类型也会放在这里。这里有一个点值得说一下,关于.d.ts 和.ts 的区别:


.d.ts 和.ts 的区别

.d.ts 是编译器从你的.ts 代码中分离出来的非 js 的部分,类似于接口定义规范。从上图中可以看出.d.ts 是给 js 文件提供类型声明的,通常来说它是 tsc 自动生成的。


react

当然还有一种情况,代码不是用 TypeScript 写的,而我们希望调用方可以得到类型信息,这时我们需要手动写.d.ts 来提供一份对外的 type。比如项目中会引入许多第三方库,而这些库是基于 JavaScript 开发的,通常这些库的类型声明文件会放到 node_modules/@type 下。比如这里的 react,就是通过.d.ts 文件来提供类型声明的。


最后回到我们的文件中,由于我们项目的特性,我们并没有大量写.d.ts 文件,但由于我们会需要用到全局声明,通常我们习惯会把全局 declare 放在.d.ts 里。


然后我们说一下通常哪些类型会被当做公共数据类型放到 lib/types 下面:


后端接口数据类型

首先是后端接口数据类型,看这个例子,这里定义的 ListAllChangeHistoryInfoResponse 就是后端返回的数据类型,其中每一项的类型是 ChangeHistoryInfo。



由于前端的页面展示的内容是以后端返回数据为基础的,这也决定了这样的类型会被多个上层类型定义所调用。



这里还有一个隐含的好处,我们在项目中期,引入了前后端接口同步方案,这个后面会提到,是我们自己发布了一个第三方 type 库来集中提供各种与接口相关的数据类型。那么在 adopt 的过程中,我们不需要全局逐个文件地改这个被替换的接口,只需要在 lib/type 下做一次这样的修改即可。


公共组件 / 通用方法的某些参数类型

还有一类是公共组件或者通用方法的某些参数的类型,从右边的代码中可以看到,这里的 TreeSelect 有一个属性是 flatOptions, 它的类型就是左侧定义的 TreeOptionItem 所组成的 list,我们在项目中一个常见的场景是请求回后端数据,经过一个数据转换的函数,把数据 format 成 option 类型的数据,传给 TreeSelect 做展示。


由于这样的场景非常多见,针对于不同的后端数据,会需要不同的数据处理,所以这个 TreeOptionItem 就会被多个上层 component 所调用。因此我们也推荐放到 lib/types 下。


最后一部分是常见问题处理,我们在项目中遇到了各种问题,在这里总结两个比较典型的问题。


当我们用 ts 编译器做类型检查时,出现 compile error 很常见,通常我们也可以通过修正 type 的定义来 fix,但如果我们 import 的是一些 css、png 这样的文件该怎么办呢?


如何 import 无类型的资源?

由于这些文件本身无法定义类型,最直接的想法是加上 @ts-except-error,这确实可以解决问题,但是需要注意,如果使用了 ts-expect-error,加下来的代码中没有真实的类型错误,编译器会提示:Unused ‘@ts-expect-error’ directive,而使用 ts-ignore 则无论下面的语句有没有编译错误,编译器都会忽略。


如何 import 无类型的资源?

但无论是哪一条命令,这样做的缺点是每次 import 都必须加,有没有一劳永逸的方式呢?


全局 declare 非 type 资源

我们推荐使用全局 declare 的方式,像一个配置文件一样,在项目初期就把这样一个文件放到 lib/types 下,那么此类问题都不会出现了。需要注意的是,全局 declare 不可以在最外层包含 import、export 这样的语句,否则它会被当做局部声明而无法全局生效。


引入的第三方库没有 type 或者 type 定义有问题怎么办?

第二个问题是引入第三方库没有 type 或者 type 定义有问题该怎么办?这里同样可以通过全局 declare 的方法解决。


把 type 提交到 DefinitelyTyped

同时,我们更加推荐把声明文件发布到 DefinitelyTyped 上,让更多的人可以受益。


作者介绍


陈芸 FreeWheel 核心业务团队高级软件工程师


主要负责前端开发工作,对前端前沿技术非常热衷,致力于提升产品质量,优化用户体验。前豆瓣全栈开发工程师,对 ToB,ToC 的项目都有深刻的理解。

2022-07-21 12:508294

评论

发布
暂无评论
发现更多内容

管控变更对提升质量的重要性

老张

质量保障 配置管理

HarmonyOS音频开发指导:使用AVPlayer开发音频播放功能

HarmonyOS开发者

HarmonyOS

深入理解 Netty FastThreadLocal

vivo互联网技术

性能优化 Netty ThreadLocal 内存泄漏 FastThreadLocal

瑞技伙伴 | ZPE 带外管理方案,稳固您的IT世界

Bytebridge

数据中心 带外管理方案 ZPE

梦幻西游手游详细图文架设教程

echeverra

梦幻西游

如何按照固定比例缩放模型

3D建模设计

3D模型 等比缩放

Elasticsearch Relevance Engine---为AI变革提供高级搜索能力[ES向量搜索、常用配置参数、聚合功能等详解]

汀丶人工智能

ES 向量检索 搜索系统 语义搜索

Mac电脑高效音频录制 Piezo 最新 for mac

mac大玩家j

Mac软件 音频录制软件 录音软件

峰会倒计时 3 天!互联网与文娱论坛演讲亮点预告!

SelectDB

数据库 大数据 数据仓库 实时数仓 apache doris

选择香港服务器发展线上业务的未来趋势:技术与市场的变化

一只扑棱蛾子

香港服务器

哪家堡垒机支持国密算法?有哪些功能?

行云管家

运维 堡垒机 安全运维 国密浏览器 国密算法

浅析“代码可视化” | 京东云技术团队

京东科技开发者

架构 字节码 企业号10月PK榜 代码可视化

产品需求交付质量保证的“七重门” | 京东云技术团队

京东科技开发者

测试 交付质量 企业号10月PK榜

研发日常踩坑-Mysql分页数据重复 | 京东云技术团队

京东科技开发者

MySQL 数据库 分页 企业号10月PK榜

C4D 2024插件:Arnold for mac(C4D S2024阿诺德渲染器) v4.6.6.1完美激活版

mac

苹果mac Windows软件 Arnold for Cinema 4D C4D R24插件

想让你的代码简洁,试试这个SimpleDateFormat类高深用法

华为云开发者联盟

Java 后端 开发 华为云 华为云开发者联盟

cpu温度监测推荐 Turbo Boost Switcher Pro激活最新版

胖墩儿不胖y

Mac软件 温度监测工具

不会写代码同学的福音——AI 代码生成器 Amazon CodeWhisperer(通过注释写代码)

亚马逊云科技 (Amazon Web Services)

人工智能 CodeWhisperer Amazon Lambda 云上探索实验室

即刻报名,企业服务与新经济论坛亮点提前揭秘!

SelectDB

数据库 大数据 数据仓库 实时数仓 apache doris

正确选择数据库安全运维平台的几个原则-行云管家

行云管家

数据库 数据安全 数据库安全 安全运维

IBM只有29%的职位看学历?基于技能的招聘到底是什么?

用友BIP

智能招聘

如何给模型换色

3D建模设计

3D模型 颜色 材质

如何修改模型粗糙度增强模型表面粗糙度

3D建模设计

3D模型 粗糙度

和鲸ModelWhale与中科可控X系列异构加速服务器完成适配认证,搭载海光芯片,构筑AI算力底座

ModelWhale

gpu 服务器 信创 算力 数据科学

对话在行人|九州通:携手用友打造招聘共享中心实现招聘数智化

用友BIP

2023全球商业创新大会 对话在行人

万字长文:拆解银行数智运营之困!

京东科技开发者

人工智能 数字化转型 金融 企业号10月PK榜

云计算进入 AI 原生时代

Baidu AICLOUD

大模型 RDMA AI 原生云

即时通讯音视频开发(二十):一文读懂视频的颜色模型转换和色域转换

JackJiang

网络编程 即时通讯 IM

OpenHarmony持久化存储UI状态:PersistentStorage

OpenHarmony开发者

OpenHarmony

高性能计算与多模态处理的探索之旅:英伟达GH200性能优化与GPT-4V的算力加速未来

GPU算力

腾讯云入选2023 Gartner分布式混合基础设施魔力象限

Geek_2d6073

从0到1开启一个全新的 TypeScript 项目_语言 & 开发_陈芸_InfoQ精选文章