react + typescript 工程化治理

工程化治理主要分为以下几个方面:

  • 静态检查:typescript + ESLint
  • 开发体验: 打包工具和Mono-repo管理
  • 代码质量: 测试

静态检查:

TS 和ESLint这些工具本质上是对代码做静态检查,尽早发现隐藏的bug
TS和ESLint的区别: TS有ESLint没有的类型检查
相同点:都具有语法错误检查的能力
TS + ES相结合:ES对代码风格做一个规范,TS主要负责对代码语法和语义上的错误进行静态检查
从AnyScript到TypeScript

用TS,一个很重要的区别就是有没有在配置中打开strict 选项
没有的话,其实使用的是AnyScript,在类型上没有约束,和JS没有太大区别,如果是从 JS 迁移到 TS 的项目,这个选项应该关闭,因为老的 JS 代码没有写类型。但如果是全新的纯 TS 项目,strict 是一定要打开的。现在 CRA 这样的脚手架创建的项目也是默认开启了 strict 模式的。

下面说说一些 strict 模式下的常见问题以及一些类型的技巧:
noImplicitAny
场景1: 函数的入参没有定义类型,含有隐式any, strict模式不允许
场景2: 没有显示声明对象的index signiture

const props = {
  foo: "bar"
}
props['foo'] = 'bar';
// Element implicitly has an 'any' type because expression 
// of type 'string' can't be used to index type

修改

interface Props {
  foo: string;
[key:string]:Props[keyof Props];
// 在key值确定的情况下,可以用keyof来获取一个接口的所有key 组成的联合类型
}
const props: Props = {
  foo: "bar",
}
props["foo"] = "bar"; // ok
props["bar"] = 'bar"; // error

strictNullChecks
场景: 获取一个可能是null或undefined变量上面的方法或属性
导致的原因: GUI场景(全局变量),如:this.graph,很多的成员变量是会在组件初始化之后才有值的,初始值这个时候就是undefined
解决方案:对于 strict 模式下的 strictNullChecks,我们可以用 type guards(if(this.graph)this.graph.on()),type assertion(!),optional chaining(?) 三种办法去告诉编译器,这里的操作是安全的。
关键是,Optional 的值在 GUI 编程中是很正常的,我们要学会去处理和面对这些情况,把 undefined 和 null 作为一个单独的类型来对待。

如何查看第三方库是否有类型定义
方法1: 查找项目中的package.json中是否有types字段
方法2: 类型定义可能是一个单独的类型包,如: @types/react
没有类型的库最好不要在TS项目中使用

高级类型
除了 Type Guard,交叉类型,联合类型和上文提到的可以为 null 的类型之外,最关键的是: Mapped types(映射类型)、Conditional Types(条件类型)、Index types(索引类型)
在使用泛型时,这些技巧可以让我们对类型进行“编程”,想象一下对类型变量使用 ?三元表达式或者 Array.prototype.map 这样的方法。
exp: 条件类型

T extends U ? X : Y   // 如果T包含U所以的属性,这个类型是X, 否则就是Y
function process(text:string|null):string|null{
  return text && text.replace(/f/g, "p")
}
process("foo").toUpperCase(); // Type error
// 解决方案: 条件类型
function process<T extends string | null >(
  text: T
): T extends string ? string : null {
  return text && text.replace(/f/g, "p")
}

ESLint 和 Prettier

ESLint + TypeScript: 取代TSLint的新方案
TSLint 在 2019 年宣布未来项目将会废弃。TS 官方推荐 ESLint 作为 Linter
如何配置让ESLint支持解析TS文件?
@typescript-eslint/parser
配套还有 @typescript/eslint-plugin 作为 ESLint 下针对 TS 订制的 Lint 规则

JS + TS混合项目 ESLint 配置
在又有 JS 又有 TS 文件的情况下,ESLint 需要只在 TS 文件上,执行 TS 相关规则的校验,不然在校验 JS 时很多 TS 规则也会生效,这样就造成了困扰。
解决方案就是使用 ESLint 的 override:

"overrides": [
  {
    "files": "**/*.ts",
    "extends": [
        "eslint-config-airbnb",
        "plugin:@typescript-eslint/recommended",
        "prettier/@typescript-eslint",
        "prettier",
        "prettier/react"
    ],
  }
]
// 只有处理TS文件时才加入TS的相关规则
// 另外,有一些 JS 规则在 TS 文件上使用时也会出现问题,比如: 
// https://github.com/eslint/eslint/issues/8813。解决方案也是使用 override。

Pre commit hook
Pre commit hook: 是指设置一个 Git hook,在提交之前运行。前端项目一般利用这个机会运行静态代码检查和代码格式化,比如 ESLint,Prettier。也可以运行测试或者 TS 编译等等检查。

具体设置的流程可以参考:Configuring Pre-commit Hooks for Prettier and Linting on a TypeScript Project。

commit hook 也可以用 -n 跳过,所以还应该在 CI 时加上 ESLint,来保证不规范的代码提交被立刻发现。

开发体验

目标:一键配置,一键升级

打包工具
关于模块的格式,我们听过 AMD,CommonJS,UMD,ES Module 等等。
Nodejs 的 CommonJS
Webpack 这样的打包工具也只兼容 CommonJS 模块
ES Module,这个标准是未来浏览器支持的标准,Nodejs 也会支持。
目前的构建工具都支持原生的 ES Module 格式(之前需要用 babel 转为 CommonJS)

构建 ES module:Rollup/Babel
我们只需要把 package.json 的 module 字段指向打包出的 es module 格式的文件,构建工具就会使用 module 字段而不是 main 字段进行构建了。
打包工具选择:
Webpack 目前不支持输出 ES module,排除
Rollup 和 Babel 是可行的两种方案。

Rollup 是目前最流行 JS 库打包工具,React,Vue 之类的开源项目都在使用 Rollup。Rollup 支持输出 CommonJS,UMD,ES Module 在内的主流格式,并可以通过插件支持 CSS 等静态资源的处理。
Rollup 和 Webpack 的主要区别就是 Rollup 是以构建 JS 为核心的,并且从一开始就是基于 ES Module 的,如果要兼容 CommonJS 代码,需要引入额外的插件。Webpack 更关注的是所有资源的构建,并且强调 Code Splitting 的能力,专注于 Web 应用的打包。Rollup 更轻量和专注,而且支持 ES Module 的输出,所有在 JS 库打包这个方面 Rollup 是首选。

Babel其实本身只是一个转译工具。但 Babel 可以通过插件支持 TS 代码的转译,还有 JSX 的转译(老本行),所以如果是简单的 TS 库,可以直接用 Babel 进行转译,输出的就是原汁原味的 ES Module(因为 Babel 压根没有去解析模块,只是单纯的转译代码)。需要注意的是 Babel 的 TS 转译只是转译,不是编译,所以类型错误是不会报出的,需要额外跑 tsc 来对 TS 代码进行类型校验。其他的静态资源也是一样的,需要单独跑 task。

专注与 JS 库打包的 Father
Father 可以简单理解为是 JS 库领域的 CRA 或者 Umi。Father 封装了 Rollup 和 Babel 两套工具链。
在最简单的情况下:我们只需要告诉 Father 需要什么格式的输出就可以构建成功,比如:

father build --esm --cjs --umd --file bar src/foo.js

mono-repo 管理:Lerna
Lerna 是用于管理拥有多个 npm package 的 mono repo 的工具。mono repo 就是指多个项目的源码放在同一个仓库下进行管理
简单的说,Lerna 的功能就是一键在多个 package 中同时运行一些命令。而且运行的时候还会根据 package 之间的依赖拓扑关系,对命令的启动顺序进行编排。同时 Lerna 的 bootstrap 命令可以把 package 之间相互的依赖,自动 link 到 package 自己的 node_modules 里面。这可以说是最大的一个卖点。Lerna 之前如果要在本地开发多个相互依赖的 npm 包,那就要敲一堆的 npm link,而且还容易出问题。

mono-repo 这种方式本身也是为了提升多个 npm package 的情况下,管理源代码的效率,以及共享基础设施。因此 Lerna 其实是提升了开发者开发基于 mono repo 的前端项目的体验。

前端的组件库一类的项目,用 Lerna 是非常合适的。

代码质量

这里的代码质量主要是指测试
React 组件测试技术选型: jest 测试框架 + Enzyme/ react-testing-library DOM Util(用于组建渲染和DOM操作)
在 React 16 下,Enzyme 有一些问题,比如 shallow 模式下不支持 useEffect。
react testing library 是在 React 官方的 test util 基础上包装的,要更轻量一些。
常见的测试技巧
react testing library 的测试套路
我们只需要调用 render ,把组件渲染出来就行了:

asTragment
queryByText
rerender
queryByTextId
act()
fireEvent

Mock 浏览器事件
Canvas 测试
如果测试的目标中有 Canvas,情况分两种:
Canvas 上的内容是组件用到的图表库一类的渲染结果,和组件本身的正确性无关
Canvas 上的内容就是测试的目标,比如给图表库写测试
如果是前者,我们可以 Mock 掉 Canvas,使用 jest-canvas-mock 可以很方便的一键 Mock。
如果是后者,我们可以用 jest-electron 去运行一个真实的浏览器,来测试 Canvas 的绘制结果。
覆盖率
Jest 配置了 collectCoverage: true 之后就会在本地生成测试覆盖率报表。用 http-server 起一个本地服务器就可以看到

补充:

ESLint + TS 这方面的资料很多,Using ESLint and Prettier in a TypeScript Project 这篇文章讲了如何从 TSLint 迁移到 ESLint。
还有以下的文章,都讲解了相关的配置(ESLint + TS):
Integrating Prettier + ESLint + Airbnb Style Guide in VSCode
Setting up ESLint with Prettier, TypeScript, and Visual Studio Code
From ESLint to TSLint and Back Again
关于 TSLint 到 ESLint 的切换的背景,可以看 typescript-eslint 这个项目的 README,讲的非常详细

使用 ESLint 的好处就是:可以背靠 ESLint 的生态,像 Airbnb 这样的规则集就可以直接用于 TS 项目。上面列举的博客就有讲如何配置 Airbnb + typescript-eslint + prettier 三种规则集。让项目可以用 typescript-eslint 来规范 TS 代码(TS 特有的 Lint 规则),用 Airbnb 来规范 React 和 JS 代码(TS 是 JS 的超集),用 Prettier 相关规则来关闭前两个规则中和 Prettier 代码风格冲突的规则。三者集合就是目前比较完善,好用的 Lint 规则了。
Airbnb 中有一些规则,比如要求 React 组件声明 PropTypes,是不适用于 TS 项目的,所以需要在 ESLint 配置文件里关掉。其他类似的配置有很多,我们不用死板的遵守 Lint 规则,而是关闭不合适的规则,只取其精华

在 React 16 下,Enzyme 有一些问题,比如 shallow 模式下不支持 useEffect。详见:https://github.com/airbnb/enzyme/issues/2086
如果对 react testing library 不熟悉,可以看官网和这篇教程。
https://testingjavascript.com/ 这个测试教程网站,可以了解到测试相关技术的大图,如果对测试的分类和作用不太清楚可以看一下这个网站。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容