ESLint 在项目中已经是大家见惯不惯的存在,你可能很厌烦动不动跳出来的 ESLint 报错,也可能很享受经过统一校验的工工整整的代码,无论如何,我的意见是,在稍微正式点的项目中都要有 ESLint 的存在,无论是直接使用简单的 recommend 配置如 extends: ['eslint: recommend']
,还是精心研究了一整套适用于自己的规则集,Lint 工具的最大帮助就是保持语法统一,至少项目中的所有 JavaScript 文件应使用统一的单双引号、分号、缩进等风格(仅靠编辑器并不能保证)。
其次,Lint 帮助你的代码更加简洁、有效,如不允许未使用的变量、JSX/TSX 中使用简写的 true 属性(<Comp shouldDisplay />
而不是 <Comp shouldDisplay={true} />
)等、还有一点值得一提,ESLint 并不会一直尝试去简化你的代码,在很多情况下它会要求你写更多代码来换取可读性和安全性的提升,尤其是在 TypeScript 场景下,explicit-module-boundary-types
规则会要求你为函数与类方法显式的声明其返回值,switch-exhaustiveness-check
规则会要求你处理联合类型变量的所有类型分支。
本文来自于我在所在团队(淘宝店铺)内部制定、落地、推广 ESLint 规则集的收获,将会简要的介绍一批我认为在 TypeScript 分享中非常有必要的规则,通过这篇文章,你会了解到在制定规则时我们考虑的是什么,对于 TypeScript 代码进行约束的思考,以及如何在自己的团队内推广这一套规则。
另外,淘系技术部前端架构团队正在淘系内推广 AppLint,准备将 ESLint 推广到整个淘系前端作为 CI/CD 的卡口之一,欢迎集团的同学了解并试用。
P.S. 我参与的 QCon+ 专题:TypeScript 在中大型项目中的落地实践[1] 中,淘宝店铺 TypeScript 研发规约落地[2] 这一课程包括了我们团队从 JavaScript 迁移到 TypeScript 以及落地完整的研发规约经验,欢迎来听~
为了适应读者可能有的不同的约束严格程度,这里将规则拆分为基础约束与严格约束部分,基础约束的规则以语法统一(包括实际代码与类型部分)为主,推荐所有人在所有项目中使用,即使是个人项目——说实在的,都写 TypeScript 了,还在意这小小的 Lint 规则?而严格约束部分更关注类型以及 ECMAScript、TypeScript 的特殊语法,适合对代码质量要求较高的同学。这里不会给出推荐的错误等级,即使全部是 warn,只要你打开了,至少你也会在以后心情好的时候来修对吧?(对吧?)
TypeScript 中支持使用 Array<T>
与 T[]
的形式声明数组类型,此规则约束项目中对这两种数组类型的声明。
其支持的配置:
Array<T>
或 T[]
其中一种T[]
,对于对象类型、函数类型等使用 Array<T>
(推荐)为什么?:对于这种效果完全一致的语法,我们需要的只是确定一个规范然后在所有地方使用这一规范。实际上,这一类规则(还有后面的类型断言语法)就类似于单引号/双引号,加不加分号这种基础规则,如果你不能接受上一行代码单引号这一行代码双引号,那么也没理由能接受这里一个 Array<number>
那里一个 number[]
,另外,我个人推荐统一使用 []
。
只允许对异步函数、Promise、PromiseLike 使用 await 调用
为什么:避免无意义的 await 调用。
禁止 @ts-
指令的使用,或者允许其在提供了说明的情况下被使用,如:
https://mp.weixin.qq.com/s/hWnuv1CmUQ5_k2wbPTO7jg
此规则推荐与 prefer-ts-expect-error
搭配使用,详见下方。
为什么:如果说乱写 any 叫 AnyScript,那么乱写 @ts-ignore
就可以叫 IgnoreScript 了。
禁止部分值被作为类型标注,此规则能够对每一种被禁用的类型提供特定的说明来在触发此规则报错时给到良好的提示,场景如禁用 {}
、Function
、object
这一类被作为类型标注,
为什么?使用 {}
会让你寸步难行:类型 {} 上不存在属性 'foo'
,所以用了 {}
你大概率在下面还需要类型断言回去或者变 any,使用 object``Function
毫无意义。
Record<string, unknown>
type SomeFunc = (arg1: string) => void
,或在未知的场景下使用 type SomeFunc = (...args: any[]) => any
。TypeScript 支持通过 as
与 <>
两种不同的语法进行类型断言,如:
// @ts-expect-error 这里的类型太复杂,日后补上
// @ts-nocheck 未完成迁移的文件
这一规则约束使用统一的类型断言语法,我个人一般在 Tsx 中使用 as
,在其他时候尽可能的使用 <>
,原因则是 <>
更加简洁。
为什么:类似于 array-type,做语法统一,但需要注意的是在 Tsx 项目中使用 <>
断言会导致报错,因为不像泛型可以通过 <T extends Foo>
来显式告知编译器这里是泛型语法而非组件。
函数与类方法的返回值需要被显式的指定,而不是依赖类型推导,如:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #c678dd;line-height: 26px;">const foo = (): <span style="line-height: 26px;"><span style="line-height: 26px;">Foo => {};<br></br>
为什么:通过显式指定来直观的区分函数的功能,如副作用等,同时显式指定的函数返回值也能在一定程度上提升 TypeScript Compiler 性能。
### no-extra-non-null-assertion
不允许额外的重复非空断言:
```
// x
function foo(bar: number | undefined) {
const bar: number = bar!!!;
}
```
```
为什么:额,why not?
prefer-for-of
在你使用 for 循环遍历数组时,如果索引仅仅用来访问数组成员,则应该替换为 for...of
。
为什么:如果不是为了兼容性场景,在这种场景下的确没有必要使用 for 循环。
prefer-nullish-coalescing && prefer-optional-chain
使用 ??
而不是 ||
,使用 a?.b
而不是 a && a.b
。
为什么:逻辑或 ||
会将 0 与 "" 视为 false 而导致错误的应用默认值,而可选链相比于逻辑与 &&
则能够带来更简洁的语法(尤其是在属性访问嵌套多层,或值来自于一个函数时,如 document.querySelector
),以及与 ??
更好的协作:const foo = a?.b?.c?.d ?? 'default';
。
no-empty-interface
不允许定义空的接口,可配置为允许单继承下的空接口:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #5c6370;font-style: italic;line-height: 26px;">// x<br></br><span style="color: #c678dd;line-height: 26px;">interface Foo {}<br></br><br></br><span style="color: #5c6370;font-style: italic;line-height: 26px;">// √<br></br><span style="color: #c678dd;line-height: 26px;">interface Foo <span style="color: #c678dd;line-height: 26px;">extends Bar {}<br></br>
为什么:没有父类型的空接口实际上就等于 `{}`,虽然我不确定你使用它是为了什么,但我能告诉你这是不对的。而单继承的空接口场景则是较多的,如先确定下继承关系再在后续添加成员。
### no-explicit-any
不允许显式的 any。
> 实际上这条规则只被设置为 warn 等级,因为真的做到一个 any 不用或是全部替换成 unknown + 类型断言 的形式成本都非常高。
推荐配合 tsconfig 的 `--noImplicitAny` (检查隐式 any)来尽可能的保证类型的完整与覆盖率。
### no-inferrable-types
不允许不必要的类型标注,但可配置为允许类的属性成员、函数的属性成员进行额外标注。
```
const foo: string = "linbudu";
class Foo {
prop1: string = "linbudu";
}
function foo(a: number = 5, b: boolean = true) {
// ...
}
```
```
为什么:对于普通变量来说,与实际赋值一致的类型标注确实是没有意义的,TypeScript 的控制流分析能很好地做到这一点,而对于函数参数与类属性,主要是为了确保一致性,即函数的所有参数(包括重载的各个声明)、类的所有属性都有类型标注,而不是仅为没有初始值的参数/属性进行标注。
no-non-null-asserted-nullish-coalescing
不允许非空断言与空值合并同时使用:bar! ?? tmp
为什么:冗余
no-non-null-asserted-optional-chain
不允许非空断言与可选链同时使用:foo?.bar!
为什么:和上一条规则一样属于冗余,同时意味着你对 !``??``?.
的理解存在着不当之处。
no-throw-literal
不允许直接 throw 一个字符串如:throw 'err'
,只能抛出 Error 或基于 Error 派生类的实例,如:throw new Error('Oops!')
。
为什么:抛出的 Error 实例能够自动的收集调用栈信息,同时借助 proposal-error-cause[3] 提案还能够跨越调用栈来附加错误原因传递上下文信息,不过,真的会有人直接抛出一个字符串吗??
no-unnecessary-type-arguments
不允许与默认值一致的泛型参数,如:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">function <span style="color: #61aeee;line-height: 26px;">foo<<span style="color: #61aeee;line-height: 26px;">T = <span style="color: #61aeee;line-height: 26px;">number>(<span style="line-height: 26px;">) {}<br></br>foo<<span style="color: #e6c07b;line-height: 26px;">number>();<br></br>
为什么:出于代码简洁考虑。
### no-unnecessary-type-assertion
不允许与实际值一致的类型断言,如:`const foo = 'foo' as string`。
为什么:你懂的。
### no-unnecessary-type-constraint
不允许与默认约束一致的泛型约束,如:`interface FooAny<T extends any> {}`。
为什么:同样是出于简化代码的考虑,在 TS 3.9 版本以后,对于未指定的泛型约束,默认使用 `unknown` ,在这之前则是 `any`,知道这一点之后你就没必要再多写 `extends unknown` 了。
### non-nullable-type-assertion-style
此规则要求在类型断言仅起到去空值作用,如对于 `string | undefined` 类型断言为 `string`时,将其替换为非空断言 `!`
```
const foo: string | undefined = "foo";
// √
foo!;
// x
foo as string;
```
```
为什么:当然是因为简化代码了!此规则的本质是检查经过断言后的类型子集是否仅剔除了空值部分,因此无需担心对于多种有实际意义的类型分支的联合类型误判。
prefer-as-const
对于常量断言,使用 as const 而不是 <const>
,这一点类似于上面的 consistent-type-assertions 规则。
prefer-literal-enum-member
对于枚举成员值,只允许使用普通字符串、数字、null、正则,而不允许变量复制、模板字符串等需要计算的操作。
为什么:虽然 TypeScript 是允许使用各种合法表达式作为枚举成员的,但由于枚举的编译结果拥有自己的作用域,因此可能导致错误的赋值,如:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #c678dd;line-height: 26px;">const imOutside = <span style="color: #d19a66;line-height: 26px;">2;<br></br><span style="color: #c678dd;line-height: 26px;">const b = <span style="color: #d19a66;line-height: 26px;">2;<br></br><span style="color: #c678dd;line-height: 26px;">enum Foo {<br></br> outer = imOutside,<br></br> a = <span style="color: #d19a66;line-height: 26px;">1,<br></br> b = a,<br></br> c = b,<br></br>}<br></br>
这里 c == Foo.b == Foo.c == 1,还是 c == b == 2 ? 观察下编译结果:
```
"use strict";
const imOutside = 2;
const b = 2;
var Foo;
(function (Foo) {
Foo[(Foo["outer"] = imOutside)] = "outer";
Foo[(Foo["a"] = 1)] = "a";
Foo[(Foo["b"] = 1)] = "b";
Foo[(Foo["c"] = 1)] = "c";
})(Foo || (Foo = {}));
```
```
懂伐小老弟?
prefer-ts-expect-error
使用 @ts-expect-error
而不是 @ts-ignore
。
为什么:@ts-ignore
与 @ts-expect-error
二者的区别主要在于,前者是 ignore,是直接放弃了下一行的类型检查而无论下一行是否真的有错误,后者则是期望下一行确实存在一个错误,并且会在下一行实际不存在错误时抛出一个错误。
这一类干涉代码检查指令的使用本就应该慎之又慎,在任何情况下都不应该被作为逃生舱门(因为它真的比 any 还好用),如果你一定要用,也要确保用的恰当。
promise-function-async
返回 Promise 的函数必须被标记为 async,此规则能够确保函数的调用方只需要处理 try/catch 或者 rejected promise 的情况。
为什么:还用解释吗?
严格约束
no-unnecessary-boolean-literal-compare
不允许对布尔类型变量与 true / false 的 === 比较,如:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #c678dd;line-height: 26px;">declare <span style="color: #c678dd;line-height: 26px;">const someCondition: <span style="color: #e6c07b;line-height: 26px;">boolean;<br></br><span style="color: #c678dd;line-height: 26px;">if (someCondition === <span style="color: #56b6c2;line-height: 26px;">true) {<br></br>}<br></br>
为什么:首先,记住我们是在写 TypeScript,所以不要想着你的变量值还有可能是 null 所以需要这样判断,如果真的发生了,那么说明你的 TS 类型标注不对哦。而且,此规则的配置项最多允许 `boolean | null` 这样的值与 true / false 进行比较,所以还是让你的类型更精确一点吧。
### consistent-type-definitions
TypeScript 支持通过 type 与 interface 声明对象类型,此规则可将其收束到统一的声明方式,即仅使用其中的一种。
为什么:先说我是怎么做得:在绝大部分场景下,使用 interface 来声明对象类型,type 应当用于声明联合类型、函数类型、工具类型等,如:
```
interface IFoo {}
type Partial = {
[P in keyof T]?: T[P];
};
type LiteralBool = "true" | "false";
```
```
原因主要有这么几点:
-
配合 naming-convention 规则(能够用于检查接口是否按照规范命名),我们能够在看见 IFoo
时立刻知道它是一个 接口,看见 Bar
时立刻知道它是一个类型别名,配置:
<pre style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
{<br></br> <span style="color: #d19a66;line-height: 26px;">"@typescript-eslint/naming-convention": [<br></br> <span style="color: #98c379;line-height: 26px;">"error",<br></br> {<br></br> <span style="color: #d19a66;line-height: 26px;">"selector": <span style="color: #98c379;line-height: 26px;">"interface",<br></br> <span style="color: #d19a66;line-height: 26px;">"format": [<span style="color: #98c379;line-height: 26px;">"PascalCase"],<br></br> <span style="color: #d19a66;line-height: 26px;">"custom": {<br></br> <span style="color: #d19a66;line-height: 26px;">"regex": <span style="color: #98c379;line-height: 26px;">"^I[A-Z]",<br></br> <span style="color: #d19a66;line-height: 26px;">"match": <span style="color: #56b6c2;line-height: 26px;">true<br></br> }<br></br> }<br></br> ]<br></br>}<br></br>
-
接口在类型编程中的作用非常局限,仅支持 extends、泛型 等简单的能力,也应当只被用于定义确定的结构体。而 Type Alias 能够使用除 extends 以外所有常见的映射类型、条件类型等类型编程语法。同时,“类型别名”的含义也意味着你实际上是使用它来归类类型(联合类型)、抽象类型(函数类型、类类型)。
method-signature-style
方法签名的声明方式有 method 与 property 两种,区别如下:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #5c6370;font-style: italic;line-height: 26px;">// method<br></br><span style="color: #c678dd;line-height: 26px;">interface T1 {<br></br> func(arg: <span style="color: #e6c07b;line-height: 26px;">string): <span style="color: #e6c07b;line-height: 26px;">number;<br></br>}<br></br><br></br><span style="color: #5c6370;font-style: italic;line-height: 26px;">// property<br></br><span style="color: #c678dd;line-height: 26px;">interface T2 {<br></br> func: <span style="line-height: 26px;">(<span style="line-height: 26px;">arg: <span style="color: #e6c07b;line-height: 26px;">string) => <span style="color: #e6c07b;line-height: 26px;">number;<br></br>}<br></br>
此规则将声明方式进行约束,推荐使用第二种的 property 方式。
为什么:首先,这两种方式被称为 method 与 property 很明显是因为其对应的写法,method 方式类似于在 Class 中定义方法,而 property 则是就像定义普通的接口属性,只不过它的值是函数类型。推荐使用 property 的最重要原因是,通过使用 属性 + 函数值 的方式定义,作为值的函数的类型能享受到更严格的类型校验( `strictFunctionTypes`\[4\]),此配置会使用逆变(*contravariance*)而非协变(*covariance*)的方式进行函数参数的检查,关于协变与逆变我后续会单独的写一篇文章,这里暂时不做展开,如果你有兴趣,可以阅读 [TypeScript 类型中的逆变协变](/jump/aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3M_X19iaXo9TXpBd056Y3lPRGswTWc9PSZtaWQ9MjI0NzQ5NTY0OCZpZHg9MSZzbj0zOTlmODRjZWVhNDY5NjQyMmZmMGY1MDE0OTJhYjdiZSZzY2VuZT0yMSN3ZWNoYXRfcmVkaXJlY3Q=)。
### consistent-type-imports
约束使用 `import type {}` 进行类型的导入,如:
```
// √
import type { CompilerOptions } from "typescript";
// x
import { CompilerOptions } from "typescript";
```
```
为什么:import type
能够帮助你更好的组织你的项目头部的导入结构(虽然 TypeScript 4.5 支持了类型与值的混合导入:import { foo, type Foo }
,但还是推荐通过拆分值导入与类型导入语句来获得更清晰地项目结构)。值导入与类型导入在 TypeScript 中使用不同的堆空间来存放,因此无须担心循环依赖(所以你可以父组件导入子组件,子组件导入定义在父组件中的类型这样)。
一个简单的、良好组织了导入语句的示例:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #c678dd;line-height: 26px;">import { useEffect } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"react";<br></br><br></br><span style="color: #c678dd;line-height: 26px;">import { Button, Dialog } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"ui";<br></br><span style="color: #c678dd;line-height: 26px;">import { ChildComp } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"./child";<br></br><br></br><span style="color: #c678dd;line-height: 26px;">import { store } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"@/store";<br></br><span style="color: #c678dd;line-height: 26px;">import { useCookie } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"@/hooks/useCookie";<br></br><span style="color: #c678dd;line-height: 26px;">import { SOME_CONSTANTS } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"@/utils/constants";<br></br><br></br><span style="color: #c678dd;line-height: 26px;">import <span style="color: #c678dd;line-height: 26px;">type { Foo } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"@/typings/foo";<br></br><span style="color: #c678dd;line-height: 26px;">import <span style="color: #c678dd;line-height: 26px;">type { Shared } <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"@/typings/shared";<br></br><br></br><span style="color: #c678dd;line-height: 26px;">import styles <span style="color: #c678dd;line-height: 26px;">from <span style="color: #98c379;line-height: 26px;">"./index.module.scss";<br></br>
### restrict-template-expressions
模板字符串中的计算表达式其返回值必须是字符串,此规则可以被配置为允许数字、布尔值、可能为 null 的值以及正则表达式,或者你也可以允许任意的值,但这样就没意思了...
为什么:在模板表达式中非字符串与数字以外的值很容易带来潜在的问题,如:
```
const arr = [1, 2, 3];
const obj = { name: "linbudu" };
// 'arr: 1,2,3'
const str1 = `arr: ${arr}`;
// 'obj: [object Object]'
const str2 = `obj: ${obj}`;
```
```
无论哪种情况都不会是你想看到的,因为这实际上已经脱离了你的掌控。推荐在规则配置中仅开启 allowNumber
来允许数字,而禁止掉其他的类型,你所需要做得应当是在把这个变量填入模板字符串中时进行一次具有实际逻辑的转化。
switch-exhaustiveness-check
switch 的判定条件为 联合类型 时,其每一个类型分支都需要被处理。如:
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #c678dd;line-height: 26px;">type PossibleTypes = <span style="color: #98c379;line-height: 26px;">"linbudu" | <span style="color: #98c379;line-height: 26px;">"qiongxin" | <span style="color: #98c379;line-height: 26px;">"developer";<br></br><br></br><span style="color: #c678dd;line-height: 26px;">let value: PossibleTypes;<br></br><span style="color: #c678dd;line-height: 26px;">let result = <span style="color: #d19a66;line-height: 26px;">0;<br></br><br></br><span style="color: #c678dd;line-height: 26px;">switch (value) {<br></br> <span style="color: #c678dd;line-height: 26px;">case <span style="color: #98c379;line-height: 26px;">"linbudu": {<br></br> result = <span style="color: #d19a66;line-height: 26px;">1;<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> }<br></br> <span style="color: #c678dd;line-height: 26px;">case <span style="color: #98c379;line-height: 26px;">"qiongxin": {<br></br> result = <span style="color: #d19a66;line-height: 26px;">2;<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> }<br></br> <span style="color: #c678dd;line-height: 26px;">case <span style="color: #98c379;line-height: 26px;">"developer": {<br></br> result = <span style="color: #d19a66;line-height: 26px;">3;<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> }<br></br>}<br></br>
为什么:工程项目中经常出现的,导致问题发生的原因就是有部分功能逻辑点仅通过口口相传,只看代码你完全不知道自己还漏了什么地方。如联合类型变量中每一条类型分支可能都需要特殊的处理逻辑。
你也可以通过 TypeScript 中的 never 类型来实现实际代码的检验:
```
const strOrNumOrBool: string | number | boolean = false;
if (typeof strOrNumOrBool === "string") {
console.log("str!");
} else if (typeof strOrNumOrBool === "number") {
console.log("num!");
} else if (typeof strOrNumOrBool === "boolean") {
console.log("bool!");
} else {
const _exhaustiveCheck: never = strOrNumOrBool;
throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
}
```
```
这里通过编译时与运行时做了两重保障,确保为联合类型新增类型分支时也需要被妥善的处理,你可以参考开头的 never 类型 文章了解更多 never 相关的使用。除了联合类型以外,你还可以通过 never 类型来确保每一个枚举成员都需要处理。
<pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block;background: url("https://mmbiz.qpic.cn/mmbiz_svg/fhicotyX5dAdsYGvcBtYqKSj9WO0qILGXTDVEUxeHLhPP97uOGTcTvBQicRRSiaph4uhbIvxibpG6xh7JIoj5qLaCHX2erQbLicyt/640?wx_fmt=svg") 10px 10px / 40px no-repeat rgb(40, 44, 52);height: 30px;width: 100%;margin-bottom: -7px;border-radius: 5px;">```
<span style="color: #c678dd;line-height: 26px;">enum PossibleType {<br></br> Foo = <span style="color: #98c379;line-height: 26px;">"Foo",<br></br> Bar = <span style="color: #98c379;line-height: 26px;">"Bar",<br></br> Baz = <span style="color: #98c379;line-height: 26px;">"Baz",<br></br>}<br></br><br></br><span style="line-height: 26px;"><span style="color: #c678dd;line-height: 26px;">function <span style="color: #61aeee;line-height: 26px;">checker(<span style="line-height: 26px;">input: PossibleType) {<br></br> <span style="color: #c678dd;line-height: 26px;">switch (input) {<br></br> <span style="color: #c678dd;line-height: 26px;">case PossibleType.Foo:<br></br> <span style="color: #e6c07b;line-height: 26px;">console.log(<span style="color: #98c379;line-height: 26px;">"foo!");<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> <span style="color: #c678dd;line-height: 26px;">case PossibleType.Bar:<br></br> <span style="color: #e6c07b;line-height: 26px;">console.log(<span style="color: #98c379;line-height: 26px;">"bar!");<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> <span style="color: #c678dd;line-height: 26px;">case PossibleType.Baz:<br></br> <span style="color: #e6c07b;line-height: 26px;">console.log(<span style="color: #98c379;line-height: 26px;">"baz!");<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> <span style="color: #c678dd;line-height: 26px;">default:<br></br> <span style="color: #c678dd;line-height: 26px;">const _exhaustiveCheck: never = input;<br></br> <span style="color: #c678dd;line-height: 26px;">break;<br></br> }<br></br>}<br></br>
以上就是我们目前在使用的部分规则,还有一批规则或是涉及到高度的定制或是适用场景狭窄,这里就不做列举了。如果你有什么想法,欢迎与我一起交流,但请注意:我不是在灌输你一定要使用什么规则,我只是在分享我们使用的规则以及考量,因此在留言前请确认不要属于此类观点,感谢你的阅读。
![](https://oss-cn-hangzhou.aliyuncs.com/codingsky/cdn/img/2022-01-24/27c6d0b39faaba8400839f1819ed39a5)
### 参考资料
\[1\]QCon+ 专题:TypeScript 在中大型项目中的落地实践: *https://qconplus.infoq.cn/2021/beijing2nth/track/1240*
\[2\]淘宝店铺 TypeScript 研发规约落地: *https://qconplus.infoq.cn/2021/beijing2nth/presentation/4161*
\[3\]proposal-error-cause: *https://github.com/tc39/proposal-error-cause*
\[4\]`strictFunctionTypes`: *https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types*
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/hWnuv1CmUQ5_k2wbPTO7jg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。