Skip to content

TypeScript 语法概述

1. TypeScript 是什么

TypeScript 提供了 JavaScript 的所有功能,并在这些功能之上添加了一层:TypeScript 的类型系统。

TypeScript 的主要好处是,它可以检查代码中的意外行为,从而降低出现错误的机会。

TypeScript 可以识别 JavaScript 语言,在许多情况下可以推断类型。例如,在创建变量并将其赋值给特定值时,TypeScript 将使用该值作为其类型。

ts
const 
helloWorld
= 'Hello World'

TypeScript 能通过上下文得知变量类型的能力,例如:

ts
const 
a
: any = 3
if (typeof
a
=== 'number') {
console
.
log
(
a
.
toFixed
())
}

这一点也叫做 类型收缩类型守卫 或者 窄化(Narrowing)

ts
declare const 
a
: number | undefined
if (
a
!==
undefined
) {
console
.
log
(
a
.
toFixed
())
}

TypeScript 的一个核心原则是类型检查基于对象的属性和行为(type checking focuses on the shape that values have)。这有时被叫做“鸭子类型”或“结构类型”(structural typing)。

2. 定义类型

interface 用于定义接口,接口用于描述对象的结构,即接口约束了对象应该具有什么成员,而不约束对象不包含什么成员。

因此,TypeScript 的 interface 实际上是鸭子类型的,即只要对象的结构和接口一致,那么这个对象就是这个接口的实例。这一点区别于 Java 的 interface

ts
interface Person {
  
name
: string
age
: number
}

type 用于定义类型别名,可以用于定义任何类型,任何可用 interface 定义的类型都可以用 type 定义。

ts
type 
Person
= {
name
: string
age
: number
}

通常情况下,项目中使用 interface 就足够了,有时我们需要更复杂的类型定义,这时候就需要使用 type

ts
interface Person {
  
name
: string
age
: number
say
: (
words
: string) => void
} type
PersonWithoutSay
=
Omit
<Person, 'say'>

一些内置类型的定义如下:

类型描述
any类型是任意类型,任意类型都可以看做 any 类型
unknown类型是类型安全的 any 类型
void类型是没有返回值的类型,或者返回值是 undefined 的类型
never类型是永远不会有值的类型
null类型是 nullundefined 的类型
undefined类型是 undefined 的类型
object类型是非原始类型的类型
boolean类型是布尔类型,只有 truefalse
number类型是数字类型,包括整数和浮点数和 NaN
string类型是字符串类型
bigint类型是大整数类型
symbol类型是符号类型

3. 组合类型

在 TypeScript 中,类型可以通过简单的组合产生复杂类型。复杂的类型的产生方式:

  • 通过 联合类型(Union)产生
  • 通过 交叉类型(Intersect)产生
  • 通过 元组类型(Tuple)产生
  • 通过 可空类型(Nullable)产生
  • 通过 泛型(Generic)产生

常量(Literal)、内置(Built-in)类型或用户定义类型通过上述方式组合可以产生更多新的类型。

3.1 联合类型

联合类型使用 | 组合,表示可以是多个中的任意一个。

ts
type 
LockStates
= 'locked' | 'unlocked'

3.2 交叉类型

如果要满足两种以上的类型,可以使用 & 来交叉两种类型。交叉类型表示这个类型满足被交叉的所有类型。

ts
type 
A
=
B
&
C

那么 A 同时具有 BC 的结构。

例如:

ts
type 
A
= 1 | 2 | 3
type
B
= 1 | 3 | 4
type
C
=
A
&
B

此时 C 的类型为 1 | 3,因为 1 | 3 同时满足 AB

但是 interface 的行为就有点不同了:

ts
interface I1 {
  
x
: number
} interface I2 {
y
: number
} type
I3
= I1 & I2
const
a
:
I3
= {
x
: 1,
y
: 2
}

为了同时满足 I1I2I3 必须同时具有这两个接口的所有属性。

3.3 元组类型

元组类型描述一个集合对象的特征,例如 [string, number] 也是一种类型。元组类型还包含一种一个或多个的语法,使用 ... 来表示可变参数(不定参数),例如:[...args: number],这在函数中很常见。

ts
type 
A
= [string, number]
type
B
= [string, ...number[]]

3.4 可空类型

可空类型在类型后面加上 ?,表示 t | undefined 的含义,注意不是 t | null

ts
interface A {
  
a
: number
b
?: string
}

b 的类型是 t | undefined。可空类型的设计和 C# 的可空类型(Nullable 类型)类似,可能的原因是 TypeScript 的作者就是 C# 的作者吧!这使得两种语言的设计异常巧妙地吻合。

3.5 泛型

泛型编程是普遍的程序设计模式。泛型是类型的模板(也就是泛化的类型,一个类型可以表示很多类型),通过泛型可以随时创建灵活的类型。同样,TypeScript 的泛型和 C# 的泛型类似。

泛型通常可以理解为一个类型模板,即新的类型是通过这个模板复制出来的(实际上的行为和语言有关),<> 的内容为模板的参数,给定参数即产出一个新的类型。

在 TypeScript 中,参数可以有默认值,这意味着 <> 也不是必须的。

ts
interface 
A
<
T
> {
x
: number |
T
y
: number
} interface
B
<
T
= string> {
x
: number |
T
y
: number
} type
A_string
=
A
<string>
type
B_string
=
B

一些例子:

  • number[] 数字型的数组
  • (number | string)[] 数字或字符串数组
  • (a: string, ...args: number) => string 以一个字符串和多个数字为参数的函数,返回字符串
  • Promise<{ data: User[], message: string }> 一个 Promise 对象
  • A<B<string>> 嵌套的泛型类型

4. 常见工具

4.1 内置工具类型

类型描述发布版本
Awaited<T>获取 Promise 的返回值4.5
Partial<T>将类型 T 的所有属性设置为可选2.1
Required<T>将类型 T 的所有属性设置为必选2.8
Readonly<T>将类型 T 的所有属性设置为只读2.1
Record<K, T>K 指定属性并由 T 指定类型的对象类型2.1
Pick<T, K>T 中选择属性 K 的类型2.1
Omit<T, K>T 中排除属性 K 的类型3.5
Exclude<UnionType, ExcludedUnion>UnionType 中排除 ExcludedUnion2.8
Extract<UnionType, IncludedUnion>UnionType 中提取 IncludedUnion2.8
NonNullable<T>T 中排除 nullundefined2.8
Parameters<T>获取函数类型 T 的参数类型3.1
ConstructorParameters<T>获取构造函数类型 T 的参数类型3.1
ReturnType<T>获取函数类型 T 的返回值类型2.8
InstanceType<T>获取构造函数类型 T 的实例类型3.1
ThisParameterType<T>获取函数类型 Tthis 类型3.3
OmitThisParameter<T>从函数类型 T 中排除 this 类型3.3
ThisType<T>用于指定 this 类型2.3
常见的类型等价表示
ts
/**
 * 使 T 中的所有属性变为可选
 */
type 
Partial
<
T
> = {
[
P
in keyof
T
]?:
T
[
P
]
} /** * 使 T 中的所有属性变为必选 */ type
Required
<
T
> = {
[
P
in keyof
T
]-?:
T
[
P
]
} /** * 使 T 中的所有属性变为只读 */ type
Readonly
<
T
> = {
readonly [
P
in keyof
T
]:
T
[
P
]
} /** * 从 T 中选取一组属性,其键在联合类型 K 中 */ type
Pick
<
T
,
K
extends keyof
T
> = {
[
P
in
K
]:
T
[
P
]
} /** * 构造一个具有 K 中所有属性且类型为 T 的类型 */ type
Record
<
K
extends keyof any,
T
> = {
[
P
in
K
]:
T
} /** * 从 T 中排除那些可赋值给 U 的类型 */ type
Exclude
<
T
,
U
> =
T
extends
U
? never :
T
/** * 从 T 中提取那些可赋值给 U 的类型 */ type
Extract
<
T
,
U
> =
T
extends
U
?
T
: never
/** * 构造一个包含 T 中除了 K 类型的属性的类型 */ type
Omit
<
T
,
K
extends keyof any> =
Pick
<
T
,
Exclude
<keyof
T
,
K
>>
/** * 从 T 中排除 null 和 undefined */ type
NonNullable
<
T
> =
T
& {}
/** * 获取函数类型的参数类型组成的元组 */ type
Parameters
<
T
extends (...
args
: any) => any> =
T
extends (...
args
: infer
P
) => any ?
P
: never
/** * 获取构造函数类型的参数类型组成的元组 */ type
ConstructorParameters
<
T
extends abstract new (...
args
: any) => any> =
T
extends abstract new (...
args
: infer
P
) => any ?
P
: never
/** * 获取函数类型的返回类型 */ type
ReturnType
<
T
extends (...
args
: any) => any> =
T
extends (...
args
: any) => infer
R
?
R
: any
/** * 获取构造函数类型的返回类型 */ type
InstanceType
<
T
extends abstract new (...
args
: any) => any> =
T
extends abstract new (...
args
: any) => infer
R
?
R
: any

另外由几个工具是用于字符串的:

  • Uppercase<StringType>
  • Lowercase<StringType>
  • Capitalize<StringType>
  • Uncapitalize<StringType>

这些工具难以使用 type 来定义,因而在 TypeScript 中是内置的,使用编译时转换来实现的。

ts
type 
A
=
Uppercase
<'hello'>

4.2 类型断言

类型断言是一种告诉编译器某个值的类型的方法,有几种比较常见的形式:

  • 泛型语法:<Type>value
  • 别名语法:as,如 value as Type,通常用于类型强制转换
  • 非空断言操作符:var!,用于告诉编译器某个值不会是 nullundefined
  • 使用 satisfies 关键字约束的类型
ts
interface User {
  
name
: string
age
: number
} const
user
= <User>{
name
: 'Alice',
age
: 30
} const
a
: any = 3
const
b
= (
a
as number).
toFixed
()

as 总是将指定对象视为另一种类型的对象,并且这两种类型是可转换的。如果类型无法转换的类型将报错。

  • 可空类型和对应的原类型
  • a | null 转换为 a
  • any 的其他任意类型,unknown 和其他任意类型

请参考 高级:TypeScript 中的类型关系控制 来了解哪些类型可以转换。如果明显不能转换的类型,需要强制转换时,可以借助 anyunknown 类型,连续两次使用 as 即可。

ts
// 将 a 视为 string
const 
a
= (3 as unknown) as string

satisfiesas 的功能类似。不同的是,它用于在确保某些表达式与某种类型匹配的同时,保留该表达式的最特定的类型以进行推理。例如:

ts
type 
User
= {
name
: string
age
: number | string
} const
a
= {
name
: 'Alice',
age
: 30
} satisfies
User

4.3 自定义类型守卫

通常使用 typeofinstanceofinis 等操作符来定义自定义类型守卫,以帮助 TypeScript 更好地收紧类型。这些类型守卫本质上也是一种类型断言。

ts
function 
isString
(
value
: string | number):
value
is string {
return typeof
value
=== 'string'
} function
example
(
value
: string | number) {
if (
isString
(
value
)) {
console
.
log
(
value
.
toUpperCase
())
} else {
console
.
log
(
value
.
toFixed
())
} }

在项目中合理地使用类型守卫和自定义守卫,可以帮助我们减少很多不必要的类型断言,同时改善代码的可读性。

还有一些技巧可以收窄类型,例如使用 as const

ts
const 
obj1
= [{
name
: 'Alice' }, {
name
: 'Bob' }]
type
Obj1
= typeof
obj1
const
obj2
= [{
name
: 'Alice' }, {
name
: 'Bob' }] as
const
type
Obj2
= typeof
obj2

as const 可以将对象的属性变为只读(相当于加上 readonly 修饰),这样就可以更好地收窄类型。下面一节将介绍更多的修饰符。

4.4 常见修饰符

在 TypeScript 完全兼容并扩展 ES6 语法,修饰符是一种用于限制类、接口、属性和方法的访问权限的关键字。常见的修饰符有:

  • public:默认修饰符,表示公共的,可以在任何地方访问。
  • private:表示私有的,只能在类内部访问。
  • protected:表示受保护的,只能在类内部和子类中访问。
  • readonly:表示只读的,只能在声明时或构造函数中赋值。
  • static:表示静态的,可以直接通过类名访问。
  • abstract:表示抽象的,只能用于抽象类和抽象方法。
ts
abstract class 
Animal
{
public
name
: string
private
age
: number
protected
weight
: number
readonly
type
: string
static
count
= 0
constructor(
name
: string,
age
: number,
weight
: number,
type
: string) {
this.
name
=
name
this.
age
=
age
this.
weight
=
weight
this.
type
=
type
Animal
.
count
++
} static
getCount
() {
return
Animal
.
count
} abstract
say
(): void
}

5. 高级特征

5.1 条件类型(Conditional Types)

  • 定义:根据条件判断选择不同的类型,基于 extends 关键字和三目运算符。
  • 语法T extends U ? X : Y
ts
type 
IsString
<
T
> =
T
extends string ? true : false
type
A
=
IsString
<"hello"> // true
type
B
=
IsString
<42> // false
// 内置工具类型示例:Exclude<T, U> type
C
=
Exclude
<"a" | "b" | "c", "a">

使用 infer 关键字可以推断类型变量,相当于提供条件下类型的占位符:

ts
// 推断函数返回值类型
type 
ReturnType
<
T
> =
T
extends (...
args
: any[]) => infer
R
?
R
: any

infer 关键字只能在条件类型中使用,通常用于函数返回值类型的推断。

ts
type 
UnpackPromise
<
T
> =
T
extends
Promise
<infer
U
> ?
U
:
T
type
Unpacked
=
UnpackPromise
<
Promise
<string>>

5.2 映射类型(Mapped Types)

  • 定义:遍历现有类型的属性,生成新类型,通常结合 keyofin 操作符。
  • 语法{ [K in Keys]: Type }
ts
interface Person {
  
name
: string
age
: number
} // 将所有属性转换为 boolean 类型 type
Flags
<
T
> = { [
K
in keyof
T
]: boolean }
type
PersonFlags
=
Flags
<Person>
// 将对象所有属性变为可选 type
Partial
<
T
> = {
[
K
in keyof
T
]?:
T
[
K
]
} type
PartialPerson
=
Partial
<Person>

可以通过添加修饰符来改变属性的特性,例如:

ts
// 将所有属性变为只读
type 
Readonly
<
T
> = {
readonly [
K
in keyof
T
]:
T
[
K
]
} // 将所有属性变为可选和只读 type
PartialReadonly
<
T
> = {
readonly [
K
in keyof
T
]?:
T
[
K
]
} // 将所有属性变为必选 type
Required
<
T
> = {
[
K
in keyof
T
]-?:
T
[
K
]
} // 将所有属性变为可变 type
Mutable
<
T
> = {
-readonly [
K
in keyof
T
]:
T
[
K
]
}

使用 as 关键字可以重新映射属性名的类型:

ts
// 给每个选中的属性添加后缀 "_list",然后设置为数组类型
type 
MappedList
<
T
,
K
extends keyof
T
& string> = {
[
P
in
K
as `${
P
}_list`]:
T
[
P
][]
} interface Person {
name
: string
age
: number
height
: number
} type
PrefixedPerson
=
MappedList
<Person, 'name' | 'age'>

例如,选择仅由 _id 结尾的属性:

ts
type 
GetIdKeys
<
T
> = {
[
K
in keyof
T
as
K
extends `${string}_id` ?
K
: never]:
T
[
K
]
} interface User {
id
: number
name
: string
age
: number
created_at
: Date
role_id
: number
tag_id
: number
} type
IdKeys
=
GetIdKeys
<User>

上述例子中,使用 never 类型可以过滤掉不符合条件的属性。

如果在映射值上使用 never 类型,则将构造不允许某些属性的一种类型:

ts
type 
GetIdKeys
<
T
> = {
[
K
in keyof
T
]:
K
extends `${string}_id` ?
T
[
K
] : never
} type
IdKeys
=
GetIdKeys
<User>
const
user
:
IdKeys
= {
role_id
: 1,
tag_id
: 2,
created_at: new
Date
()
Type 'Date' is not assignable to type 'never'.
}

5.3 索引类型(Index Types)

  • 定义:通过 keyof 和索引访问符 T[K] 动态操作对象类型- 。
ts
interface Person {
  
name
: string
age
: number
} // 获取 Person 的键的联合类型 type
Keys
= keyof Person
// 获取某个键对应的类型 type
NameType
= Person["name"]
// 泛型函数示例:获取对象属性值 function
getProp
<
T
,
K
extends keyof
T
>(
obj
:
T
,
key
:
K
):
T
[
K
] {
return
obj
[
key
]
}

5.4 可辨识联合类型(Discriminated Unions)

  • 定义:通过公共的“标签字段”(如 kind)区分联合类型中的不同成员- 。
ts
interface Circle {
  
kind
: "circle"
radius
: number
} interface Square {
kind
: "square"
sideLength
: number
} type
Shape
= Circle | Square
function
getArea
(
shape
:
Shape
): number {
switch (
shape
.
kind
) {
case "circle": return
Math
.
PI
*
shape
.
radius
** 2
case "square": return
shape
.
sideLength
** 2
} }

5.5 枚举类型(Enums)

  • 定义:定义一组命名的常量值,分为数字枚举和字符串枚举。
  • 语法enum EnumName { ... }
ts
// 数字枚举
enum 
Color
{
Red
, // 0
Green
, // 1
Blue
// 2
} // 使用示例 const
color
:
Color
=
Color
.
Red
// 字符串枚举 enum
Direction
{
Up
= "UP",
Down
= "DOWN"
} // 使用示例 const
dir
:
Direction
=
Direction
.
Up

5.6 Symbol 类型(Symbol Types)

  • 定义:通过 symbolunique symbol 声明唯一的标识符类型。
ts
// 普通 symbol
const 
key1
=
Symbol
("key")
type
KeyType
= typeof
key1
// symbol
// unique symbol(仅允许声明时初始化) const
key2
: unique symbol =
Symbol
("key")
interface Obj { [
key2
]: string // 使用 unique symbol 作为键
} // 示例对象 const
obj
: Obj = { [
key2
]: "secret" }