进阶语法
加号减号
一些内置类型中,可能会出现+
或者-
这些符号,例如:
type Required<T> = {
[P in keyof T]-?: T[P]
}
type Person = {
name: string;
age?: number;
}
// 结果:{ name: string; age: number; }
type result = Required<Person>
type Required<T> = {
[P in keyof T]-?: T[P]
}
type Person = {
name: string;
age?: number;
}
// 结果:{ name: string; age: number; }
type result = Required<Person>
-?
是去掉类型中属性后面的?
,整个Required
的实际效果是去掉T
类型中所有属性键后面的?
,让所有属性变成必填的
in
in
操作符的右侧通常跟一个联合类型,可以使用in
来迭代这个联合类型,如下:
// 仅演示使用
in 'name' | 'age' | 'sex'
'name' // 第一次迭代结果
'age' // 第二次迭代结果
'sex' // 第三次迭代结果
// 仅演示使用
in 'name' | 'age' | 'sex'
'name' // 第一次迭代结果
'age' // 第二次迭代结果
'sex' // 第三次迭代结果
keyof
keyof T
表示获取T
类型中所有属性键,这些属性键组合成一个联合类型,例如:
type Person = {
name: string;
age: number;
}
// 'name' | 'age'
type result = keyof Person
type Person = {
name: string;
age: number;
}
// 'name' | 'age'
type result = keyof Person
TS
中的keyof T
,有点类似JavaScript
中的Object.keys()
,它们的共同点都是获取属性键的集合,只不过keyof T
得到的结果是一个联合类型,而Object.keys()
得到的是一个数组
in keyof
[P in keyof T]
:表示遍历T
中的每一个属性键,每次遍历时属性键取名为P
,这和JavaScript
中的for in
非常类似:
type required<T> = {
[p in keyof T]-?: T[p]
}
type required<T> = {
[p in keyof T]-?: T[p]
}
extends
一般有两种用法:条件类型和类型约束
条件类型
常见的条件类型表现形式如下:
T extends U ? 'Y' : 'N'
T extends U ? 'Y' : 'N'
条件类型有点像JavaScript
中的三元表达式,事实上它们的工作原理是类似的,例如:
type res1 = true extends boolean ? true : false // true
type res2 = 'name' extends 'name'|'age' ? true : false // true
type res3 = [1, 2, 3] extends { length: number; } ? true : false // true
type res4 = [1, 2, 3] extends Array<number> ? true : false // true
type res1 = true extends boolean ? true : false // true
type res2 = 'name' extends 'name'|'age' ? true : false // true
type res3 = [1, 2, 3] extends { length: number; } ? true : false // true
type res4 = [1, 2, 3] extends Array<number> ? true : false // true
在条件类型中,有一个特别需要注意的东西就是:分布式条件类型,如下:
type Extract<T, U> = T extends U ? T : never;
type type1 = 'name'|'age'
type type2 = 'name'|'address'|'sex'
// 结果:'name'
type test = Extract<type1, type2>
// 推理步骤
'name'|'age' extends 'name'|'address'|'sex' ? T : never
step1: ('name' extends 'name'|'address'|'sex' ? 'name' : never) => 'name'
step2: ('age' extends 'name'|'address'|'sex' ? 'age' : never) => never
result: 'name' | never => 'name'
type Extract<T, U> = T extends U ? T : never;
type type1 = 'name'|'age'
type type2 = 'name'|'address'|'sex'
// 结果:'name'
type test = Extract<type1, type2>
// 推理步骤
'name'|'age' extends 'name'|'address'|'sex' ? T : never
step1: ('name' extends 'name'|'address'|'sex' ? 'name' : never) => 'name'
step2: ('age' extends 'name'|'address'|'sex' ? 'age' : never) => never
result: 'name' | never => 'name'
代码详解:
T extends U ? T : never
:因为T
是一个联合类型,所以这里适用于分布式条件类型的概念。根据其概念,在实际的过程中会把T
类型中的每一个子类型进行迭代,如下:
// 第一次迭代:
'name' extends 'name'|'address'|'sex' ? 'name' : never
// 第二次迭代:
'age' extends 'name'|'address'|'sex' ? 'age' : never
// 第一次迭代:
'name' extends 'name'|'address'|'sex' ? 'name' : never
// 第二次迭代:
'age' extends 'name'|'address'|'sex' ? 'age' : never
- 在迭代完成之后,会把每次迭代的结果组合成一个新的联合类型(根据
never
类型的特点,最后的结果会剔除掉never
),如下:
type result = 'name' | never => 'name'
type result = 'name' | never => 'name'
extends keyof
类型约束,经常和泛型一起使用:
U extends keyof T
U extends keyof T
keyof T
是一个整体,它表示一个联合类型。U extends Union
这一整段表示U
的类型被收缩在一个联合类型的范围内
interface Person {
age: number
name: string
}
const person: Person = {
age: 22,
name: "Tobias"
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const name1 = getProperty(person, "name")
const gender = getProperty(person, "sex") //报错
interface Person {
age: number
name: string
}
const person: Person = {
age: 22,
name: "Tobias"
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const name1 = getProperty(person, "name")
const gender = getProperty(person, "sex") //报错
infer
infer
关键词的作用是延时推导,它会在类型未推导时进行占位,等到真正推导成功后,它能准确的返回正确的类型
为了更好的理解infer
关键词的用法,使用ReturnType
这个例子来说明,ReturnType
是一个用来获取函数返回类型的工具。
type ReturnType<T> = T extends (...args: any) => infer R ? R : never
const add = (a: number, b: number): number => {
return a + b
}
// 结果: number
type result = ReturnType<typeof add>
type ReturnType<T> = T extends (...args: any) => infer R ? R : never
const add = (a: number, b: number): number => {
return a + b
}
// 结果: number
type result = ReturnType<typeof add>
代码详解:
T extends (...args: any) => infer R
:如果不看infer R
,这段代码实际表示:T
是不是一个函数类型。(...args: any) => infer R
:这段代码实际表示一个函数类型,其中把它的参数使用args
来表示,把它的返回类型用R
来进行占位。 如果T
满足是一个函数类型,那么我们返回其函数的返回类型,也就是R
;如果不是一个函数类型,就返回never
typeof
获取一个变量的类型,通常用于获取一个对象
或函数
的类型
const add = (a: number, b: number): number => {
return a + b
}
const obj = {
name: 'AAA',
age: 23
}
// 结果:(a: number, b:number) => number
type t1 = typeof add
// 结果:{ name: string; age: number; }
type t2 = typeof obj
const add = (a: number, b: number): number => {
return a + b
}
const obj = {
name: 'AAA',
age: 23
}
// 结果:(a: number, b:number) => number
type t1 = typeof add
// 结果:{ name: string; age: number; }
type t2 = typeof obj
空值判断运算符
??
与 js中的空值判断运算符一样
同样是只有运算符左侧的值为null或undefined时,才会返回右侧的值,否则返回左侧的值
const value: number = 0
const value1: string = ""
const value2: number = null
const value3: string = null
console.log(value ?? "value为空值") // 0
console.log(value1 ?? "value为空值") // ""
console.log(value2 ?? "value为空值") // value为空值
console.log(value3 ?? "value为空值") // value为空值
const value: number = 0
const value1: string = ""
const value2: number = null
const value3: string = null
console.log(value ?? "value为空值") // 0
console.log(value1 ?? "value为空值") // ""
console.log(value2 ?? "value为空值") // value为空值
console.log(value3 ?? "value为空值") // value为空值
[name:type]:type
在不确定对象属性名与对应属性值类型的情况下,形参设置对象的属性名为string
类型(一般情况下都是string
类型),而属性值的类型则可能有很多种
interface ccache {
[a:string]:number
}
const cache:ccache = {}
// 类型正确
cache.name = 1
// 不报错
cache.name = "1"
interface ccache {
[a:string]:number
}
const cache:ccache = {}
// 类型正确
cache.name = 1
// 不报错
cache.name = "1"
常用内置类型
位于node_modules/typescript/lib/lib.es5.d.ts
中
Readonly
把每个属性都变成只读
// 使用
type A = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}
// 实现
type Readonly<T> = {
readonly [P in keyof T]: T[P] // keyof获取T上的键值集合 in表示循环keyof获取的键值
}
// 使用
type A = {a:number, b:string}
type A1 = Readonly<A> // {readonly a: number;readonly b: string;}
// 实现
type Readonly<T> = {
readonly [P in keyof T]: T[P] // keyof获取T上的键值集合 in表示循环keyof获取的键值
}
Pick
只保留自己选择的属性
type A = {a:number, b:string}
type A1 = Pick<A, 'a'> // {a:number}
type A = {a:number, b:string}
type A1 = Pick<A, 'a'> // {a:number}
Omit
排除已选的属性
type A = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
type A = {a:number, b:string}
type A1 = Omit<A, 'a'> // {b:string}
Partial
通过泛型让目标类型中的所有属性变为可选
type A = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}
type A = {a:number, b:string}
type A1 = Partial<A> // { a?: number; b?: string;}
Required
通过泛型让目标类型中的所有属性变为必选
type A = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}
type A = {a?:number, b?:string}
type A1 = Required<A> // { a: number; b: string;}
Record
创建一个类型,Record<K, T>,K代表新类型键的类型,T代表新类型值的类型
// 示例一,对象a键的类型为字符串,值的类型为数字
type A1 = Record<string, number>
const a: A1 = {
a: 1
}
// 示例二,对象result键的类型是"home" | "about" | "contact",值的类型为PageInfo
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" },
};
// 示例三,对象result键的类型是 'A' | 'B' | 'C',值的类型为数字
type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
A: 1,
B: 2,
C: 3
}
// 示例一,对象a键的类型为字符串,值的类型为数字
type A1 = Record<string, number>
const a: A1 = {
a: 1
}
// 示例二,对象result键的类型是"home" | "about" | "contact",值的类型为PageInfo
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
about: { title: "about" },
contact: { title: "contact" },
home: { title: "home" },
};
// 示例三,对象result键的类型是 'A' | 'B' | 'C',值的类型为数字
type keys = 'A' | 'B' | 'C'
const result: Record<keys, number> = {
A: 1,
B: 2,
C: 3
}
实现
type Record<K extends keyof any, T> = {
[P in K]: T
}
type Record<K extends keyof any, T> = {
[P in K]: T
}
ReturnType
获取T的返回值的类型
type A1= ReturnType<()=>number> // number
type A1= ReturnType<()=>number> // number
NonNullable
剔除undefined和null
type A1 = NonNullable<number | string | null | undefined> // number|string
type A1 = NonNullable<number | string | null | undefined> // number|string
Exclude
过滤T中和K相同(或兼容)的类型
type A = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉
type A = {a:number, b:string}
type A1 = Exclude<number|string, string|number[]> // number
type A2 = Exclude<number|string, any|number[]> // never , 因为any兼容number, 所以number被过滤掉
Extract
用法
Extract<T, U>
用来取联合类型T
和U
的交集,用法如下:
type Person = {
name: string;
age: number;
address: string;
}
// 结果:'age'|'address'
type ExtractResult = Extract<keyof Person, 'age'|'address'|'sex'>
type Person = {
name: string;
age: number;
address: string;
}
// 结果:'age'|'address'
type ExtractResult = Extract<keyof Person, 'age'|'address'|'sex'>
实现方式
type MyExtract<T, U> = T extends U ? T : never
type MyExtract<T, U> = T extends U ? T : never
代码详解:
T extends U
:此代码会自动将T
的子类型进行分发,例如:
T extends U
=> 'name'|'age'|'address' extends 'age'|'address'|'sex' ? T : never
=> (
'name' extends 'age'|'address'|'sex' ? 'name' : never |
'age' extends 'age'|'address'|'sex' ? 'age' : never |
'address' extends 'age'|'address'|'address' ? 'age' : never
)
=> 'age'|'address'
T extends U
=> 'name'|'age'|'address' extends 'age'|'address'|'sex' ? T : never
=> (
'name' extends 'age'|'address'|'sex' ? 'name' : never |
'age' extends 'age'|'address'|'sex' ? 'age' : never |
'address' extends 'age'|'address'|'address' ? 'age' : never
)
=> 'age'|'address'
declare
1.全局定义类型
2.覆盖默认类型
解构赋值设置类型
const { dst }: { dst: string } = data.trans_result[0]
const { dst }: any = data.trans_result[0]
const { dst }: { dst: string } = data.trans_result[0]
const { dst }: any = data.trans_result[0]
is类型保护
在使用类型保护时,TS 会进一步缩小变量的类型。例子中,将类型从 any 缩小至了 string
function isString(test: string): boolean {
return typeof test === "string"
}
function example(foo: any) {
if (isString(foo)) {
// 编译不会出错,运行时出错
console.log(foo.push(2))
}
}
example("hello world")
function isString(test: string): boolean {
return typeof test === "string"
}
function example(foo: any) {
if (isString(foo)) {
// 编译不会出错,运行时出错
console.log(foo.push(2))
}
}
example("hello world")
function isString(test: string): test is string {
return typeof test === "string"
}
function example(foo: any) {
if (isString(foo)) {
// ts直接提示 类型“string”上不存在属性“push”
console.log(foo.push(2))
}
}
example("hello world")
function isString(test: string): test is string {
return typeof test === "string"
}
function example(foo: any) {
if (isString(foo)) {
// ts直接提示 类型“string”上不存在属性“push”
console.log(foo.push(2))
}
}
example("hello world")
unknown
很多情况下, 我们可以使用 unknown 来替代 any , 既灵活, 又可以继续保证类型安全
export const isString = (val: unknown): val is string => typeof val === 'string'
export const isString = (val: unknown): val is string => typeof val === 'string'
声明文件
用于为已有js或ts文件提供类型声明,如果多个ts文件都用到相同的类型,就适合创建一个类型声明文件,实现类型共享
- 创建后缀名为.d.ts的类型声明文件
- 使用export导出类型
- ts文件中通过import导入,并且可以省略.d.ts后缀名
- 如果不省略.d.ts后缀,ts会报错,提示导入路径不能以“.d.ts”扩展名结束。考虑改为导入“./type.js”。ts(2691)
- 因此类型声明文件应该避免与ts文件同名,导致import时引用错误
示例
新建type.d.ts
,export导出
type Props = { x: number, y: number }
export { Props }
type Props = { x: number, y: number }
export { Props }
新建a.ts
,import导入类型
import { Props } from "./type"
const a: Props = { x: 1, y: 2 }
import { Props } from "./type"
const a: Props = { x: 1, y: 2 }