根据对 90,000 名开发人员的 Stack Overflow 调查,TypeScript 是人们最想学习的框架之一。
在过去几年中,TypeScript 的受欢迎程度,社区规模和采用率都在不断提高。 目前,Facebook 的 Facebook Jest 项目正在向 TypeScript 转移。
TypeScript 是什么?
TypeScript 是 JavaScript 的静态类型超集,提供了类型系统和对 ES6 的支持,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上
为什么使用 Typescript?
JavaScript 在过去几年中发展了很多。 它是用于客户端和服务器端的最通用的跨平台语言。
但 JavaScript 从未意味着进行如此大规模的应用程序开发。 它是一种没有类型系统的动态语言,这意味着变量可以具有任何类型的值,例如字符串或布尔值。
类型系统可提高代码质量,可读性,并使代码库的维护和重构更容易。 更重要的是,错误可以在编译时而不是在运行时捕获。
如果没有类型系统,很难扩展 JavaScript 以构建复杂的应用程序,而大型团队则使用相同的代码。
TypeScript 在编译时提供代码不同部分之间的保证。 编译器错误通常会告诉您确切的错误位置以及出现了什么问题,而运行时错误伴随着堆栈跟踪可能会产生误导并导致花费大量时间在调试工作上。
TypeScript 专业的地方
- 在编译阶段实时捕获潜在错误。
- 管理大型代码库。
- 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、,更容易重构等。
- 它可以减少开发人员的认知负担,它可以定义从简单到复杂的几乎一切类型,让团队更容易工作
- 文档 - 类型提供了一些开发人员可以遵循的文档,大部分的函数看看类型的定义就可以知道如何使用了
TypeScript 不足的地方
- 学习成本较高,要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念,需要在项目周期和维护成本中权衡,大家可以根据自己团队和项目的情况判断是否需要使用 TypeScript。
- 类型错误可能不一致,短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本。
- 集成到构建流程需要一些工作量。
类型
布尔值
const isLoading: boolean = false
数值
const decimal: number = 8
const binary: number = 0b110
字符串
const fruit: string = 'orange'
数组
数组类型可以用以下两种方式之一编写:
// 常见的
let firstFivePrimes: number[] = [2, 3, 5, 7, 11]
// 不常见。 使用泛型类型(稍后会详细介绍)
let firstFivePrimes2: Array<number> = [2, 3, 5, 7, 11]
元组
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。元组起源于函数编程语言(如 F#),在这些语言中频繁使用元组。
let contact: [string, number] = ['John', 954683]
contact = ['Ana', 842903, 'extra argument'] /* Error!
Type '[string, number, string]' is not assignable to type '[string, number]'. */
任意值
any 与类型系统中的任何和所有类型兼容,这意味着可以将任何内容分配给它,并且可以将其分配给任何类型。它使您能够选择不通过类型检查。
let variable: any = 'a string';
variable = 5;
variable = false;
variable.someRandomMethod(); /_ Okay,
someRandomMethod might exist at runtime. _/
Void
void 是没有任何类型的。它通常用作不返回值的函数的返回类型。
function sayMyName(name: string): void {
console.log(name)
}
sayMyName('Heisenberg')
Never
never 类型表示从未发生的值的类型。例如,never 函数的返回类型将始终抛出异常或未达到其终点。
// throws an exception
function error(message: string): never {
throw new Error(message)
}
// unreachable end point
function continuousProcess(): never {
while (true) {
// ...
}
}
Null and Undefined
可以使用 null 和 undefined 来定义这两个原始数据类型,它们本身并不是非常有用,但是当它们在联合类型中使用时会变得非常有用 (等下会有更多内容)
type someProp = string | null | undefined
未知类型
TypeScript 3.0 引入了未知类型,它是任何类型安全的对应类型。 任何东西都可以分配给未知的,但是未知的东西除了本身和任何东西外都不能分配。 如果没有先声明或缩小为更具体的类型,则不允许对未知操作进行操作。
type I1 = unknown & null // null
type I2 = unknown & string // string
type U1 = unknown | null // unknown
type U2 = unknown | string // unknown
类型别名
类型别名提供类型注释的名称,允许您在多个位置使用它。它们使用以下语法创建:
type Login = string
联合类型
TypeScript 允许我们为属性使用多种数据类型。这称为联合类型。
type Password = string | number
混合类型
混合类型是组合所有成员类型的属性的类型
interface Person {
name: string
age: number
}
interface Worker {
companyId: string
}
type Employee = Person & Worker
const bestOfTheMonth: Employee = {
name: 'Peter',
age: 39,
companyId: '123456'
}
接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。 在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。 附注:接口的运行时 JS 影响为零,它仅用于类型检查。
您可以声明标记带有?的可选属性,这意味着接口的对象可能定义这些属性,也可能不定义这些属性。 您可以声明只读属性,这意味着一旦为属性分配了值,就无法更改它。
interface ICircle {
readonly id: string
center: {
x: number
y: number
}
radius: number
color?: string // 可选属性
}
const circle1: ICircle = {
id: '001',
center: {
x: 0
},
radius: 8
}
/* Error! Property 'y' is missing in type '{ x: number; }'
but required in type '{ x: number; y: number; }'. */
const circle2: ICircle = {
id: '002',
center: {
x: 0,
y: 0
},
radius: 8
} // Okay
circle2.color = '#666' // Okay
circle2.id = '003'
/* Error!
Cannot assign to 'id' because it is a read-only property. */
扩展接口
接口可以扩展一个或多个接口。这使得编写接口变得灵活且可重用。
interface ICircleWithArea extends ICircle {
getArea: () => number
}
const circle3: ICircleWithArea = {
id: '003',
center: { x: 0, y: 0 },
radius: 6,
color: '#fff',
getArea: function() {
return this.radius ** 2 * Math.PI
}
}
实现接口
实现接口的类需要严格遵循接口的结构。
interface IClock {
currentTime: Date
setTime(d: Date): void
}
class Clock implements IClock {
currentTime: Date = new Date()
setTime(d: Date) {
this.currentTime = d
}
constructor(h: number, m: number) {}
}
枚举
一个 enum(或枚举)是组织相关的值,可以是数值或字符串值的集合的方式。
enum CardSuit {
Clubs,
Diamonds,
Hearts,
Spades
}
let card = CardSuit.Clubs
card = 'not a card suit' /* Error! Type '"not a card suit"'
is not assignable to type 'CardSuit'. */
Under the hood(不会翻译), 枚举默认情况下是基于数字的。enum 值从零开始,每个成员递增 1。
我们之前的示例生成的 JavaScript 代码:
var CardSuit
;(function(CardSuit) {
CardSuit[(CardSuit['Clubs'] = 0)] = 'Clubs'
CardSuit[(CardSuit['Diamonds'] = 1)] = 'Diamonds'
CardSuit[(CardSuit['Hearts'] = 2)] = 'Hearts'
CardSuit[(CardSuit['Spades'] = 3)] = 'Spades'
})(CardSuit || (CardSuit = {}))
/**
* 这导致以下对象:
* {
* 0: "Clubs",
* 1: "Diamonds",
* 2: "Hearts",
* 3: "Spades",
* Clubs: 0,
* Diamonds: 1,
* Hearts: 2,
* Spades: 3
* }
*/
或者,可以使用字符串值初始化枚举,这是一种更易读的方法。
enum SocialMedia {
Facebook = 'FACEBOOK',
Twitter = 'TWITTER',
Instagram = 'INSTAGRAM',
LinkedIn = 'LINKEDIN'
}
反向映射
enum 支持反向映射,这意味着我们可以从其值中访问成员的值以及成员名称。 映射到我们的 CardSuit 示例:
const clubsAsNumber: number = CardSuit.Clubs // 3
const clubsAsString: string = CardSuit[0] // 'Clubs'
Functions(函数)
您可以为每个参数添加类型,然后添加到函数本身以添加返回类型。
function add(x: number, y: number): number {
return x + y
}
函数重载
TypeScript 允许您声明函数重载。基本上,您可以使用相同名称但不同参数类型和返回类型的多个函数。请考虑以下示例:
function padding(a: number, b?: number, c?: number, d?: any) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a
} else if (c === undefined && d === undefined) {
c = a
d = b
}
return {
top: a,
right: b,
bottom: c,
left: d
}
}
每个参数的含义根据传递给函数的参数数量而变化。而且,该函数只需要一个,两个或四个参数。要创建函数重载,只需多次声明函数头。最后一个函数头是一个实际上是活跃中的函数体,但不是提供给外面的世界。
function padding(all: number)
function padding(topAndBottom: number, leftAndRight: number)
function padding(top: number, right: number, bottom: number, left: number)
function padding(a: number, b?: number, c?: number, d?: number) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a
} else if (c === undefined && d === undefined) {
c = a
d = b
}
return {
top: a,
right: b,
bottom: c,
left: d
}
}
padding(1) // Okay
padding(1, 1) // Okay
padding(1, 1, 1, 1) // Okay
padding(1, 1, 1) /* Error! No overload expects 3 arguments, but
overloads do exist that expect either 2 or 4 arguments. */
Classes
您可以向属性和方法的参数添加类型
class Greeter {
greeting: string
constructor(message: string) {
this.greeting = message
}
greet(name: string) {
return `Hi ${name}, ${this.greeting}`
}
}
访问修饰符
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。 public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的 private 修饰的属性或方法是私有的,不能在声明它的类的外部访问 protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
| Accessible on | public | protected | private |
|---|---|---|---|
| class | yes | yes | yes |
| class children | yes | yes | no |
| class instance | yes | no | no |
只读修饰符
A readonly property must be initialised at their declaration or in the constructor.
class Spider {
readonly name: string
readonly numberOfLegs: number = 8
constructor(theName: string) {
this.name = theName
}
}
参数属性
参数属性允许您在一个位置创建和初始化成员。它们通过在构造函数参数前加上修饰符来声明。
class Spider {
readonly numberOfLegs: number = 8
constructor(readonly name: string) {}
}
抽象
abstract 关键字既可用于类,也可用于抽象类方法。
- 抽象类不能直接实例化。它们主要用于继承,其中扩展抽象类的类必须定义所有抽象方法。
- 抽象成员不包含实现,因此无法直接访问。这些成员必须在子类中实现(有点像接口)
键入断言
TypeScript 允许您以任何方式覆盖其推断类型。当您比变换器本身更好地理解变量类型时,可以使用此方法。
const friend = {}
friend.name = 'John' // Error! Property 'name' does not exist on type '{}'
interface Person {
name: string
age: number
}
const person = {} as Person
person.name = 'John' // Okay
最初类型断言的语法是<type>
let person = <Person>{}
但是这在 JSX 中使用时产生了歧义。因此建议改为使用 as。
类型断言通常在从 JavaScript 迁移代码时使用,您可能知道变量的类型比当前分配的更准确。但断言可被视为有害。
我们来看看上一个示例中的 Person 接口。你注意到了什么问题吗?如果您注意到失踪的房产年龄,恭喜!编译器可能会帮助您为 Person 的属性提供自动完成功能,但如果您错过任何属性,它将不会抱怨。
类型推断
当没有类型注释形式的可用显式信息时,TypeScript 会推断变量类型。
/**
* 变量定义
*/
let a = 'some string'
let b = 1
a = b /** Error! Type 'number' is not assignable to type 'string'. **/
//如果是复杂对象,TypeScript会查找最常见的类型
//推断对象的类型。
const arr = [0, 1, false, true] // (number | boolean)[]
/**
* 函数返回类型
*/
function sum(x: number, y: number) {
return x + y // 推断返回一个数字
}
类型兼容性
类型兼容性基于结构类型,结构类型仅基于其成员关联类型。 结构类型的基本规则 x 是与 yif y 至少具有相同成员的兼容 x。
interface Person {
name: string
}
let x: Person // Okay, 尽管不是Person接口的实现
let y = { name: 'John', age: 20 } // type { name: string; age: number }
x = y
//请注意x仍然是Person类型。
//在以下示例中,编译器将显示错误消息,因为它不会
//期望在Person中的属性年龄,但结果将和预期的一样
console.log(x.age) // 20
与 y 成员一样 name: string,它匹配 Person 接口的必需属性,这意味着它 x 是一个子类型 y。因此,允许分配。
Functions
参数的个数
在函数调用中,您需要传入至少足够的参数,这意味着额外的参数不会导致任何错误。
function consoleName(person: Person) {
console.log(person.name)
}
consoleName({ name: 'John' }) // Okay
consoleName({ name: 'John', age: 20 }) //也可以是额外的参数
返回类型
返回类型必须至少包含足够的数据。
let x = () => ({ name: 'John' })
let y = () => ({ name: 'John', age: 20 })
x = y // OK
y = x /* Error! Property 'age' is missing in type '{ name: string; }'
but required in type '{ name: string; age: number; }' */
类型守卫
类型守卫允许您缩小条件块中对象的类型。
类型
在条件块中使用 typeof,编译器将知道变量的类型是不同的。在下面的示例中,TypeScript 了解在条件块之外,x 可能是布尔值,并且 toFixed 无法在其上调用该函数。
function example(x: number | boolean) {
if (typeof x === 'number') {
return x.toFixed(2)
}
return x.toFixed(2) // Error! Property 'toFixed' does not exist on type 'boolean'.
}
instanceof
class MyResponse {
header = 'header example'
result = 'result example'
// ...
}
class MyError {
header = 'header example'
message = 'message example'
// ...
}
function example(x: MyResponse | MyError) {
if (x instanceof MyResponse) {
console.log(x.message) // Error! Property 'message' does not exist on type 'MyResponse'.
console.log(x.result) // Okay
} else {
// TypeScript knows this must be MyError
console.log(x.message) // Okay
console.log(x.result) // Error! Property 'result' does not exist on type 'MyError'.
}
}
in
该 in 操作员检查的对象上的属性的存在。
interface Person {
name: string
age: number
}
const person: Person = {
name: 'John',
age: 28
}
const checkForName = 'name' in person // true
文字类型
文字是精确值,是 JavaScript 原语。它们可以组合在一个类型联合中以创建有用的抽象。
type Orientation = 'landscape' | 'portrait'
function changeOrientation(x: Orientation) {
// ...
}
changeOrientation('portrait') // Okay
changeOrientation('vertical') /* Error! Argument of type '"vertical"' is not
assignable to parameter of type 'Orientation'. */
条件类型
条件类型描述类型关系测试,并根据该测试的结果选择两种可能类型中的一种。
type X = A extends B ? C : D
这意味着如果 type A 可以赋值给 type B,那么 X 它的类型是 C。否则 X 与类型相同 D;
通用类型
通用类型是必须包含或引用其他类型才能完成的类型。它强制执行各种变量之间的有意义约束。 在以下示例中,函数返回您传入的任何类型的数组。
function reverse<T>(items: T[]): T[] {
return items.reverse()
}
reverse([1, 2, 3]) // number[]
reverse([0, true]) // (number | boolean)[]
keyof
该 keyof 运营商查询组给定类型的钥匙。
interface Person {
name: string
age: number
}
type PersonKeys = keyof Person // 'name' | 'age'
映射类型
映射类型允许您通过映射属性类型从现有类型创建新类型。根据您指定的规则转换现有类型的每个属性。
Partial
type Partial<T> = { [P in keyof T]?: T[P] }
- 通用部分类型使用单个类型参数定义 T。
- keyof T 表示 T 字符串文字类型的所有属性名称的并集。
- [P in keyof T]?: T[P]表示类型的每个属性 P 的类型 T 应该是可选的并转换为 T[P]。
- T[P]表示类型属性 P 的类型 T。
Readonly
正如我们在 Interface 部分中所介绍的,TypeScript 允许您创建只读属性。有一种 Readonly 类型,它接受一个类型 T 并将其所有属性设置为只读。
type Readonly<T> = { readonly [P in keyof T]: T[P] }
Exclude
Exclude 允许您从其他类型中删除某些类型。Exclude 来自 T 任何可分配的东西 T。
/**
* type Exclude<T, U> = T extends U ? never : T;
*/
type User = {
_id: number
name: string
email: string
created: number
}
type UserNoMeta = Exclude<keyof User, '_id' | 'created'>
Pick
Pick 允许您从其他类型中选择某些类型。Pick 来自 T 任何可分配的东西 T。
/**
* type Pick<T, K extends keyof T> = {
* [P in K]: T[P];
* };
*/
type UserNoMeta = Pick<User, 'name' | 'email'>
infer
您可以使用 infer 关键字在 extends 条件类型的子句中推断类型变量。此类推断类型变量只能用于条件类型的 true 分支。
返回类型
获取函数的返回类型。
/**
* 原始TypeScript的ReturnType
* type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
*/
type MyReturnType<T> = T extends (...args: any) => infer R ? R : any
type TypeFromInfer = MyReturnType<() => number> // number
type TypeFromFallback = MyReturnType<string> // any
分析一下 MyReturnType:
- 返回类型 T 是......
- 首先,是 T 一个功能?
- 如果是,则类型解析为推断的返回类型 R。
- 否则类型解析为 any。
原文:www.freecodecamp.org/news/the-de…
参考:ts.xcatliu.com/
翻译不准确,或者难以理解的地方大家可以指出,万分感谢