大家好,我是暮云,在前端开发不断演进的今天,TypeScript 已经逐渐从"可选技能"变成了许多团队的"默认要求"。
如果你在使用 JavaScript,却时常被一些难以定位的 bug 困住,或者在维护大型代码时感到混乱不堪,那么 TypeScript 将是一个非常值得学习的工具
这篇文章非常的简洁,用最短的文字介绍了TypeScript的各种语法,如果你会使用简单的TypeScript,又不是很熟练,也可以看一看,尤其是对象类型,三元运算符,索引访问,infer推断,扩展这几个标题下的内容,可能会有新的收获,也可以把这篇文章当作一个文档,当某些语法忘记了可以来看一下
接下来我们便进入正文,首先,我们在学习TypeScript时不要把它当成JavaScript的扩展,而是要把它当成一个新的语言来学习,这样的话你就会更容易的理解范型,typeof,keyof等关键字语法了
基础类型
首先列举一些 TypeScript 的数据类型有:string,number,boolean,null,undefined,any,unknown,void,never,object
这些就不一一介绍了,只看一些不常见的
unknown:表示未知类型,使用前必须明确判断类型
void:表示一个函数无返回值
never:表示一个不可能的值
这些类型在后边都会详细介绍
字面类型
字面类型是指:一个值本身就是一个类型
示例:
'hello' -> { 'hello' }
1 -> { 1 }
->前面表示的是类型,后面大括号中包着的便是符合类型的值,后面的例子都用这种形式表示
联合类型
联合类型使用 | 运算符进行组合
示例:
1 | 2 -> { 1, 2 }
string | number -> { 'a', 'b', ..., 1, 2, ... }
子类型
当A类型中所有值都存在于B中,那么就可以说这个A是B的子类型
示例:
true extends boolean
string extends any
元组类型
元组是什么:元组是一个固定长度,固定类型顺序的数组
示例:
typescript
type T1 = [boolean, string]
const a: T1 = [true, 'a']
对象类型
对象类型:用于描述一个对象应该拥有哪些属性,以及这些属性的类型是什么
当我们声明一个对象类型时,只要对象 至少满足声明的属性要求 ,就被视为合法类型,额外的属性是允许的(注意,额外的属性是允许的!)
示例:
{ a: boolean } -> { { a: true }, { a: false }, { a: true, b: 1 } }
这个示例里面的{ a: true, b: 1 }是符合{ a: boolean }类型的
想必大家在写下面这段代码时都会遇到类型报错的问题
typescript
const obj: { a: boolean } = {
a: true,
b: 1
}
报错:"b"不在类型"{ a: boolean; }"中
如果你是这样写的话,就完全不会有这种问题
typescript
const t = {
a: true,
b: 1
}
const obj: { a: boolean } = t
这是为什么呢?下面我来讲一下原因
在ts中,字面量赋值会触发更严格的额外属性检查 ,因为你如果写字面量的话很有可能会写错单词,比如将method写成mothod,一旦这个属性写错,是很难被发现的bug,所以TS 触发额外属性检查,是为了阻止低级错误
交叉类型
交叉类型用 & 拼接,它表示同时符合这些类型的值
示例:
{ a: number } & { b: number } -> { a: number, b: number }
类型别名
类型别名使用 type 关键字声明
示例:
typescript
type T = 1 | 2 | 3
const a:T = 1 // 还可以是2 3
范型
范型可以让我们在定义函数、接口、类时,不预先限定具体类型,而是在使用时再传入需要的类型
示例:
typescript
type I<T> = T
const a:I<number> = 1
范型就像是我们在写 TypeScript 版的函数,调用传参
typeof
typeof 可以用来获取变量或对象的类型信息,用于类型推断或类型保护
示例:
typescript
const str = "hello"
type T = typeof str // T -> string
as const
有时候我们希望对象或数组的每个属性都被推断为字面量类型而不是普通类型 ,这时可以使用 as const
示例:
typescript
const obj = { a: 1, b: 2 } as const
// obj 的类型是 { readonly a: 1; readonly b: 2 }
const arr = [1, 2, 3] as const
// arr 的类型是 readonly [1, 2, 3]
它的作用就是:
- 把对象/数组里的值变成字面量类型
- 所有属性变成
readonly,不能被修改
unknown 和 any
any:表示任意类型,关闭类型检查,随意使用,但容易出错unknown:表示未知类型,使用前必须进行类型判断,更安全
typescript
let x: any = 1
x.toFixed() // 没问题
let y: unknown = 1
y.toFixed() // 报错,必须先判断类型
let y: unknown = 1
if(typeof y === 'number') y.toFixed() // 没问题
keyof
keyof 用于获取某个对象类型的所有键,返回一个联合类型
typescript
type Obj = {a: number, b: string}
type Keys = keyof Obj // 'a' | 'b'
索引访问
索引访问类型可以通过类型获取对象属性类型,非常方便
typescript
type Obj = {a: number, b: string}
type A = Obj['a'] // number
type B = Obj['b'] // string
type C = Obj[keyof Obj] // number | string
type Arr = [string,number,boolean]
type D = Arr[0] // string
type E = Arr[number] // string | number | boolean
映射
映射类型用于 [K in ...] 遍历键并创建新的对象类型
typescript
type Keys = 'a' | 'b' | 'c'
type Obj = {[K in Keys]: number}
// Obj -> {a:number, b:number, c:number}
这个语法把他当成js中的遍历是不是就很好理解了
三元运算符
TypeScript 支持条件类型,其语法类似 js 的三元运算符
typescript
type IsString<T> = T extends string ? true : false
type A = IsString<'hello'> // true
type B = IsString<123> // false
// 当对联合类型进行求值时,TypeScript 会分别处理每个成员,然后将结果合并在一起
type C = IsString<('1'|1)> // boolean
// 使用 never 过滤掉不想要的分支
type Filter<T, Match> = T extends Match ? T : never;
type D = Filter<number | string, string> // string
type E = Filter<1 | 'a' | 'b', string> // 'a' | 'b'
注意:每个类型都被视为自身的子类型
typescript
string extends string ? true : false // true
infer推断
infer 用于条件类型中推断类型,特别适合提取类型信息
typescript
type FlattenArray<T> = T extends (infer Item)[] ? Item : T
type A = FlattenArray<number[]> // number
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type R = ReturnType<() => string> // string
- 第一个例子很好理解,我们来看一下第二个例子
- 首先给
ReturnType传递参数:() => string,一个函数类型 - 然后
T extends (...args: any[]) => infer R,判断这个T,也就是传递的那个函数是否属于(...args: any[]) => infer R,infer推断出返回值为string,所以R的值为string - 最终判断为
true,返回string类型
扩展
ok,我们把基本 TypeScript 的语法学完了,下面写两个实用的示例
筛选
typescript
type Pick<T, K extends keyof T> = { [P in K]: T[P] }
type A = Pick<{a: number, b: string}, 'a'> // { a: number }
never的用法
typescript
type Fruit = 'apple' | 'banana'
function eat(fruit: Fruit) {
if (fruit === 'apple') {
console.log('吃苹果')
} else if (fruit === 'banana') {
console.log('吃香蕉')
} else {
// 这里是不可能发生的分支
const _check: never = fruit
console.log(_check)
}
}
如果将来 fruit 的值新增了一个,那么这段程序就会报错,这样你就会知道这里需要添加新的分支了