类型系统
TypeScript 的核心是其强大的类型系统,它能在编译时捕获错误,提供更好的代码提示和文档。
基础类型
原始类型
typescript
// 字符串
let name: string = 'Tom'
// 数字
let age: number = 25
let hex: number = 0xf00d
let binary: number = 0b1010
// 布尔值
let isDone: boolean = false
// null 和 undefined
let n: null = null
let u: undefined = undefined
// Symbol
let sym: symbol = Symbol('key')
// BigInt
let big: bigint = 100n特殊类型
typescript
// any - 任意类型,绕过类型检查
let notSure: any = 4
notSure = 'string' // OK
notSure.foo() // OK,但运行时可能报错
// unknown - 安全的 any,需要类型检查后才能使用
let value: unknown = 'hello'
// value.length // 错误:不能直接使用
if (typeof value === 'string') {
console.log(value.length) // OK
}
// void - 没有返回值
function log(msg: string): void {
console.log(msg)
}
// never - 永不返回
function error(msg: string): never {
throw new Error(msg)
}
function infiniteLoop(): never {
while (true) {}
}any vs unknown
typescript
// any 可以赋值给任何类型
let a: any = 1
let b: string = a // OK,但可能运行时出错
// unknown 不能直接赋值给其他类型
let x: unknown = 1
// let y: string = x // 错误
let y: string = x as string // 需要断言或类型收窄对象类型
对象字面量
typescript
// 内联类型
let user: { name: string; age: number } = {
name: 'Tom',
age: 25
}
// 可选属性
let config: {
host: string
port?: number // 可选
} = { host: 'localhost' }
// 只读属性
let point: {
readonly x: number
readonly y: number
} = { x: 10, y: 20 }
// point.x = 5 // 错误:只读
// 索引签名
let dict: {
[key: string]: number
} = {
apple: 1,
banana: 2
}type 类型别名
typescript
type User = {
name: string
age: number
email?: string
}
type ID = string | number
type Point = {
x: number
y: number
}
const user: User = {
name: 'Tom',
age: 25
}interface 接口
typescript
interface User {
name: string
age: number
email?: string
readonly id: number
}
// 方法定义
interface Calculator {
add(a: number, b: number): number
subtract: (a: number, b: number) => number
}
// 索引签名
interface StringArray {
[index: number]: string
}
const arr: StringArray = ['a', 'b', 'c']type vs interface
typescript
// interface 可以重复声明(合并)
interface User {
name: string
}
interface User {
age: number
}
// User = { name: string; age: number }
// type 不能重复声明
type Point = { x: number }
// type Point = { y: number } // 错误
// interface 可以 extends
interface Animal {
name: string
}
interface Dog extends Animal {
breed: string
}
// type 使用交叉类型
type Animal = { name: string }
type Dog = Animal & { breed: string }
// type 可以定义联合类型、元组等
type Status = 'pending' | 'success' | 'error'
type Tuple = [string, number]
// 推荐:对象结构用 interface,其他用 type数组和元组
数组类型
typescript
// 两种写法
let numbers: number[] = [1, 2, 3]
let strings: Array<string> = ['a', 'b', 'c']
// 联合类型数组
let mixed: (string | number)[] = [1, 'a', 2, 'b']
// 对象数组
interface User {
name: string
}
let users: User[] = [{ name: 'Tom' }]
// 只读数组
let readonlyArr: readonly number[] = [1, 2, 3]
// readonlyArr.push(4) // 错误元组类型
元组是固定长度和类型的数组:
typescript
// 基本元组
let tuple: [string, number] = ['hello', 10]
// 可选元素
let optionalTuple: [string, number?] = ['hello']
// 剩余元素
let restTuple: [string, ...number[]] = ['hello', 1, 2, 3]
// 命名元组(提高可读性)
type Point = [x: number, y: number]
const point: Point = [10, 20]
// 只读元组
let readonlyTuple: readonly [string, number] = ['hello', 10]
// 实际应用:函数返回多个值
function useState<T>(initial: T): [T, (value: T) => void] {
let state = initial
const setState = (value: T) => { state = value }
return [state, setState]
}
const [count, setCount] = useState(0)函数类型
函数声明
typescript
// 参数和返回值类型
function add(a: number, b: number): number {
return a + b
}
// 可选参数
function greet(name: string, greeting?: string): string {
return `${greeting || 'Hello'}, ${name}`
}
// 默认参数
function greet2(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}`
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0)
}函数类型表达式
typescript
// 类型别名
type AddFn = (a: number, b: number) => number
const add: AddFn = (a, b) => a + b
// 接口定义
interface MathFn {
(a: number, b: number): number
}
// 调用签名(带属性的函数)
type FnWithDescription = {
description: string
(a: number): number
}
const double: FnWithDescription = (n) => n * 2
double.description = 'Doubles a number'函数重载
typescript
// 重载签名
function format(value: string): string
function format(value: number): string
function format(value: Date): string
// 实现签名
function format(value: string | number | Date): string {
if (typeof value === 'string') {
return value.trim()
} else if (typeof value === 'number') {
return value.toFixed(2)
} else {
return value.toISOString()
}
}
format('hello') // OK
format(3.14159) // OK
format(new Date()) // OKthis 类型
typescript
interface User {
name: string
greet(this: User): void
}
const user: User = {
name: 'Tom',
greet() {
console.log(`Hello, ${this.name}`)
}
}
// const greet = user.greet
// greet() // 错误:this 不是 User 类型字面量类型
typescript
// 字符串字面量
type Direction = 'up' | 'down' | 'left' | 'right'
let dir: Direction = 'up'
// dir = 'forward' // 错误
// 数字字面量
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6
let roll: DiceRoll = 3
// 布尔字面量
type True = true
type False = false
// 模板字面量类型
type EventName = `on${Capitalize<'click' | 'focus' | 'blur'>}`
// 'onClick' | 'onFocus' | 'onBlur'
// as const 断言
const config = {
host: 'localhost',
port: 3000
} as const
// typeof config = { readonly host: 'localhost'; readonly port: 3000 }联合类型和交叉类型
联合类型
typescript
// 基础联合
type ID = string | number
function printId(id: ID) {
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id.toFixed(2))
}
}
// 判别联合
type Circle = {
kind: 'circle'
radius: number
}
type Square = {
kind: 'square'
size: 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.size ** 2
}
}交叉类型
typescript
// 合并类型
type Named = { name: string }
type Aged = { age: number }
type Person = Named & Aged
const person: Person = {
name: 'Tom',
age: 25
}
// 接口继承的替代
interface Printable {
print(): void
}
interface Loggable {
log(): void
}
type PrintableLoggable = Printable & Loggable类型收窄
TypeScript 通过控制流分析自动收窄类型:
typescript
function example(value: string | number | null) {
// typeof 收窄
if (typeof value === 'string') {
return value.toUpperCase() // value: string
}
// 真值收窄
if (value) {
return value.toFixed(2) // value: number
}
// 相等收窄
if (value === null) {
return 'null' // value: null
}
}
// in 收窄
interface Bird {
fly(): void
}
interface Fish {
swim(): void
}
function move(animal: Bird | Fish) {
if ('fly' in animal) {
animal.fly() // animal: Bird
} else {
animal.swim() // animal: Fish
}
}
// instanceof 收窄
function logDate(value: Date | string) {
if (value instanceof Date) {
console.log(value.toISOString()) // value: Date
} else {
console.log(new Date(value).toISOString()) // value: string
}
}类型谓词
typescript
interface Cat {
meow(): void
}
interface Dog {
bark(): void
}
// 自定义类型守卫
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal
}
function speak(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow() // animal: Cat
} else {
animal.bark() // animal: Dog
}
}类型断言
typescript
// as 语法
const input = document.getElementById('input') as HTMLInputElement
input.value = 'Hello'
// 尖括号语法(JSX 中不可用)
const input2 = <HTMLInputElement>document.getElementById('input')
// 非空断言
function process(value: string | null) {
// 确信 value 不为 null
console.log(value!.length)
}
// const 断言
const arr = [1, 2, 3] as const // readonly [1, 2, 3]
const obj = { x: 10 } as const // { readonly x: 10 }常见面试题
1. any 和 unknown 的区别?
any可以赋值给任何类型,也可以访问任何属性unknown更安全,必须进行类型检查后才能使用- 推荐用
unknown代替any
2. type 和 interface 的区别?
interface可以重复声明(声明合并),type不行interface使用extends继承,type使用&交叉type可以定义联合类型、元组、原始类型别名- 推荐:对象用
interface,其他用type
3. never 类型有什么用?
- 表示永远不会有值的类型
- 抛出异常的函数返回
never - 无限循环的函数返回
never - 在穷尽检查中确保处理了所有情况
4. 如何实现类型收窄?
typeof检查原始类型instanceof检查类实例in检查属性存在- 相等性检查
- 自定义类型守卫(类型谓词)