TypeScript 基础知识到类型体操
文章基于 v5.8.3 版本
Typescript 的好处:
提前在编辑器知道变量的类型,而不用去打印才知道
类型安全,减少运行时错误,比如有个值是 undefined,没有 TS 可能导致线上报错花不少时间才找到问题
编辑器 typescript 配置提示
配置后,编辑器会自动提示变量的类型,方便及时发现问题


- vscode 插件 typescript imports 可以自动导入模块
- ts-node 可以运行 ts 文件
- ts-node-dev 可以热更新 ts 文件
tsconfig.json 配置
下面是当前博客 Nextjs 项目的 tsconfig.json 配置,我查阅了文档,给配置增加了注释,方便理解 TS 在项目中的使用
{
"compilerOptions": {
// 编译选项
"target": "ES2017", // 目标版本
"lib": ["dom", "dom.iterable", "esnext"], // 内置类型,配置下就能用
"allowJs": true, // 允许在项目中导入 JavaScript 文件,而不仅仅是 .ts 和 .tsx 文件。例如,此 JS 文件:
"skipLibCheck": true, // 跳过库文件的类型检查
"strict": true, // 开启所有严格检查,默认 strictNullChecks noImplicitAny
"noEmit": true, // 不编译出文件
"esModuleInterop": true, // https://www.typescriptlang.org/tsconfig/#esModuleInterop
"module": "esnext", // 模块
"moduleResolution": "bundler", // 模块解析策略
"resolveJsonModule": true, // 允许导入 JSON 模块
"isolatedModules": true, // 每个文件被视为一个模块
"jsx": "preserve", // 保留 JSX 语法
"incremental": true, // 增量编译
"plugins": [
// 插件
{
"name": "next"
}
],
"paths": {
// 路径别名
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], // 包含的文件
"exclude": ["node_modules"] // 排除的文件
}{
"compilerOptions": {
// 编译选项
"target": "ES2017", // 目标版本
"lib": ["dom", "dom.iterable", "esnext"], // 内置类型,配置下就能用
"allowJs": true, // 允许在项目中导入 JavaScript 文件,而不仅仅是 .ts 和 .tsx 文件。例如,此 JS 文件:
"skipLibCheck": true, // 跳过库文件的类型检查
"strict": true, // 开启所有严格检查,默认 strictNullChecks noImplicitAny
"noEmit": true, // 不编译出文件
"esModuleInterop": true, // https://www.typescriptlang.org/tsconfig/#esModuleInterop
"module": "esnext", // 模块
"moduleResolution": "bundler", // 模块解析策略
"resolveJsonModule": true, // 允许导入 JSON 模块
"isolatedModules": true, // 每个文件被视为一个模块
"jsx": "preserve", // 保留 JSX 语法
"incremental": true, // 增量编译
"plugins": [
// 插件
{
"name": "next"
}
],
"paths": {
// 路径别名
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], // 包含的文件
"exclude": ["node_modules"] // 排除的文件
}type 和 interface 的区别
type 是别名不能扩展,interface 是可扩展的,interface + extends 性能更好,也就是用来继承
1、在 TypeScript 版本 4.2 之前,type 可能会出现在错误消息中 ,有时会代替等效的匿名类型(这可能是可取的,也可能不可取的)。interface 将始终在错误消息中命名。
2、type 不能参与声明合并,但 interface 可以 。
interface Mammal {
genus: string
}
interface Mammal {
breed?: string
}
const animal: Mammal = {
genus: '1234',
breed: 1
}
type Reptile = {
genus: string
}
// 不能这样 ❌
type Reptile = {
breed?: string
}interface Mammal {
genus: string
}
interface Mammal {
breed?: string
}
const animal: Mammal = {
genus: '1234',
breed: 1
}
type Reptile = {
genus: string
}
// 不能这样 ❌
type Reptile = {
breed?: string
}3、接口只能用于声明对象的形状,而不能用于重命名基元 。
// Interface 方式
interface AnObject1 {
value: string
}
// Type 方式
type AnObject2 = {
value: string
}
// 两种方式都可以用来描述对象的结构,功能基本相同。
// ✅ type 可以为现有的基础类型创建别名
type SanitizedString = string
type EvenNumber = number
// ❌ interface 不能这样做
interface X extends string {
// 这会报错!interface 不能继承基础类型
}// Interface 方式
interface AnObject1 {
value: string
}
// Type 方式
type AnObject2 = {
value: string
}
// 两种方式都可以用来描述对象的结构,功能基本相同。
// ✅ type 可以为现有的基础类型创建别名
type SanitizedString = string
type EvenNumber = number
// ❌ interface 不能这样做
interface X extends string {
// 这会报错!interface 不能继承基础类型
}4、interface 将始终以其原始形式显示在错误消息中,但前提是它们按 name 使用。
interface Mammal {
name: string
}
function echoMammal(m: Mammal) {
console.log(m.name)
}
// 错误信息会明确提到 "Mammal" 这个接口名称
echoMammal({ name: 12343 })
// m 的类型和上面的 Mammal 完全相同,但没有被直接命名
function echoAnimal(m: { name: string }) {
console.log(m.name)
}
echoAnimal({ name: 12345 })
// 错误信息类似:
// 类型 'number' 不能赋值给类型 'string'。
// 参数类型 '{ name: number }' 不能赋值给参数类型 '{ name: string }'。
// 当对象类型很复杂时,这个区别就非常明显interface Mammal {
name: string
}
function echoMammal(m: Mammal) {
console.log(m.name)
}
// 错误信息会明确提到 "Mammal" 这个接口名称
echoMammal({ name: 12343 })
// m 的类型和上面的 Mammal 完全相同,但没有被直接命名
function echoAnimal(m: { name: string }) {
console.log(m.name)
}
echoAnimal({ name: 12345 })
// 错误信息类似:
// 类型 'number' 不能赋值给类型 'string'。
// 参数类型 '{ name: number }' 不能赋值给参数类型 '{ name: string }'。
// 当对象类型很复杂时,这个区别就非常明显5、对于编译器来说,使用带有 extends 的接口通常比使用带有交集的类型别名性能更高
preferring-interfaces-over-intersections
大概是说交集的 type 会递归合并,而 interface 不会只会 flat,然后 interface 会缓存
类型断言
如使用 document.getElementById 的时候,TypeScript 只知道这将返回某种 HTMLElement,但你可能知道你的页面将始终具有具有给定 ID 的 HTMLCanvasElement
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElementconst myCanvas = document.getElementById('main_canvas') as HTMLCanvasElementobject、Object 以及
- Object 包含了所有的类型,避免使用
- object 表示所有非原始类型的类型,即数组、对象与函数类型
- {} 表示空对象类型,任何非 null / undefined 的值,不可赋值,避免使用
泛型
泛型允许我们在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型。这样可以让我们写出更加通用和类型安全的代码。
泛型约束 🔥
使用 extends 关键字来约束泛型类型:
// 约束 T 必须有 length 属性
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length) // 现在我们知道 arg 有 length 属性
return arg
}
// ✅ 正确使用
loggingIdentity('hello') // string 有 length
loggingIdentity([1, 2, 3]) // array 有 length
// ❌ 错误使用
// loggingIdentity(3) // number 没有 length 属性// 约束 T 必须有 length 属性
interface Lengthwise {
length: number
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length) // 现在我们知道 arg 有 length 属性
return arg
}
// ✅ 正确使用
loggingIdentity('hello') // string 有 length
loggingIdentity([1, 2, 3]) // array 有 length
// ❌ 错误使用
// loggingIdentity(3) // number 没有 length 属性映射类型与泛型
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
// 将所有属性变为只读
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
// 将所有属性变为可选
type MyPartial<T> = {
[P in keyof T]?: T[P]
}
// 将数组中的每个元素转换为对象的键
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
[P in T[number]]: P
}
interface User {
name: string
age: number
}
type PartialUser = MyPartial<User> // { name?: string; age?: number }
type ReadonlyUser = MyReadonly<User> // { readonly name: string; readonly age: number }type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
// 将所有属性变为只读
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
// 将所有属性变为可选
type MyPartial<T> = {
[P in keyof T]?: T[P]
}
// 将数组中的每个元素转换为对象的键
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
[P in T[number]]: P
}
interface User {
name: string
age: number
}
type PartialUser = MyPartial<User> // { name?: string; age?: number }
type ReadonlyUser = MyReadonly<User> // { readonly name: string; readonly age: number }实际应用场景
// API 响应类型
interface ApiResponse<T> {
data: T
success: boolean
message: string
}
// 使用
type UserResponse = ApiResponse<User>
type UsersResponse = ApiResponse<User[]>
// 异步函数返回类型
async function fetchUser(id: string): Promise<ApiResponse<User>> {
// 实现...
}
// 异步函数返回类型
async function fetchUsers(): Promise<ApiResponse<User[]>> {
// 实现...
}// API 响应类型
interface ApiResponse<T> {
data: T
success: boolean
message: string
}
// 使用
type UserResponse = ApiResponse<User>
type UsersResponse = ApiResponse<User[]>
// 异步函数返回类型
async function fetchUser(id: string): Promise<ApiResponse<User>> {
// 实现...
}
// 异步函数返回类型
async function fetchUsers(): Promise<ApiResponse<User[]>> {
// 实现...
}函数重载
函数重载允许为同一个函数提供多个类型签名,让 TypeScript 能够根据参数类型推断出正确的返回类型。
基本语法
// 重载签名(只有类型声明)
function process(value: string): string
function process(value: number): number
function process(value: boolean): string
// 实现签名(具体实现)
function process(value: string | number | boolean): string | number {
if (typeof value === 'string') {
return value.toUpperCase()
}
if (typeof value === 'number') {
return value * 2
}
return value.toString()
}
// 使用时 TypeScript 会根据参数类型推断返回类型
const result1 = process('hello') // string
const result2 = process(42) // number
const result3 = process(true) // string// 重载签名(只有类型声明)
function process(value: string): string
function process(value: number): number
function process(value: boolean): string
// 实现签名(具体实现)
function process(value: string | number | boolean): string | number {
if (typeof value === 'string') {
return value.toUpperCase()
}
if (typeof value === 'number') {
return value * 2
}
return value.toString()
}
// 使用时 TypeScript 会根据参数类型推断返回类型
const result1 = process('hello') // string
const result2 = process(42) // number
const result3 = process(true) // string实际应用场景
// DOM 元素获取
function getElement(selector: string): HTMLElement | null
function getElement(element: HTMLElement): HTMLElement
function getElement(input: string | HTMLElement): HTMLElement | null {
if (typeof input === 'string') {
return document.querySelector(input)
}
return input
}// DOM 元素获取
function getElement(selector: string): HTMLElement | null
function getElement(element: HTMLElement): HTMLElement
function getElement(input: string | HTMLElement): HTMLElement | null {
if (typeof input === 'string') {
return document.querySelector(input)
}
return input
}内置工具类型
// 返回 Promise 的值类型
Awaited<T>
// 可选
Partial<T>
// 必选
Required<T>
// 只读
Readonly<T>
// 将 K 中的每个属性值映射到 V,返回一个对象类型,可以使用 Record 类型来声明属性名还未确定的接口类型
Record<K, V>
// 从 T 中选择一组属性 K,返回一个新类型
Pick<T, K>
// 从 T 中排除一组属性 K,返回一个新类型
Omit<T, K>
// 从 UnionType T 中排除 U 中的类型,T是联合类型,返回一个新类型
Exclude<T, U>
// 从 UnionType T 中提取 U 中的类型,T是联合类型,返回一个新类型
Extract<T, U>
// 返回函数参数的类型
Parameters<T>
// 返回函数返回值的类型
ReturnType<T>// 返回 Promise 的值类型
Awaited<T>
// 可选
Partial<T>
// 必选
Required<T>
// 只读
Readonly<T>
// 将 K 中的每个属性值映射到 V,返回一个对象类型,可以使用 Record 类型来声明属性名还未确定的接口类型
Record<K, V>
// 从 T 中选择一组属性 K,返回一个新类型
Pick<T, K>
// 从 T 中排除一组属性 K,返回一个新类型
Omit<T, K>
// 从 UnionType T 中排除 U 中的类型,T是联合类型,返回一个新类型
Exclude<T, U>
// 从 UnionType T 中提取 U 中的类型,T是联合类型,返回一个新类型
Extract<T, U>
// 返回函数参数的类型
Parameters<T>
// 返回函数返回值的类型
ReturnType<T>Pick 与 Omit 类型是类型编程中相当重要的一个部分,举例来说,我们可以先声明一个代表全局所有状态的大型接口类型:
type User = {
name: string
age: number
email: string
phone: string
address: string
gender: string
occupation: string
education: string
hobby: string
bio: string
}type User = {
name: string
age: number
email: string
phone: string
address: string
gender: string
occupation: string
education: string
hobby: string
bio: string
}然后在我们的子组件中,可能只用到了其中一部分的类型,此时就可以使用 Pick 类型将我们需要的部分择出来:
type UserBasicInfo = Pick<User, 'name' | 'age' | 'email'>type UserBasicInfo = Pick<User, 'name' | 'age' | 'email'>反之,如果我们用到了大部分类型,只有数个类型需要移除,就可以使用 Omit 类型来减少一些代码量:
type UserDetailedInfo = Omit<User, 'name' | 'age' | 'email'>type UserDetailedInfo = Omit<User, 'name' | 'age' | 'email'>模版字符串类型
此前使用字面量类型来提供精确的类型定义时,会面临的一个问题就是当可用的字面量类型过多,自己一个个写会非常的头痛,可能就直接选择用 string 类型了。而现在,假设这些字面量类型满足一定的规律,我们就可以通过模板字符串类型的自动分发特性,来实现由排列组合自动生成联合类型了!
type Version = `${number}.${number}.${number}`
const v1: Version = '1.1.0'
const v2: Version = '1.0' // 报错:类型 "1.0" 不能赋值给类型 `${number}.${number}.${number}`
const v3: Version = 'a.0.0' // 报错:类型 "a.0" 不能赋值给类型 `${number}.${number}.${number}`type Version = `${number}.${number}.${number}`
const v1: Version = '1.1.0'
const v2: Version = '1.0' // 报错:类型 "1.0" 不能赋值给类型 `${number}.${number}.${number}`
const v3: Version = 'a.0.0' // 报错:类型 "a.0" 不能赋值给类型 `${number}.${number}.${number}`type Brand = 'iphone' | 'xiaomi' | 'honor'
type SKU = `${Brand}` // "iphone" | "xiaomi" | "honor"
type SKU = `${Brand}-latest` // "iphone-latest" | "xiaomi-latest" | "honor-latest"
type Brand2 = 'iphone' | 'xiaomi' | 'honor'
type Memory = '16G' | '64G'
type ItemType = 'official' | 'second-hand'
type SKU2 = `${Brand2}-${Memory}-${ItemType}` // 多种排列组合type Brand = 'iphone' | 'xiaomi' | 'honor'
type SKU = `${Brand}` // "iphone" | "xiaomi" | "honor"
type SKU = `${Brand}-latest` // "iphone-latest" | "xiaomi-latest" | "honor-latest"
type Brand2 = 'iphone' | 'xiaomi' | 'honor'
type Memory = '16G' | '64G'
type ItemType = 'official' | 'second-hand'
type SKU2 = `${Brand2}-${Memory}-${ItemType}` // 多种排列组合infer
infer 关键字用于在条件类型中推断类型,可以从复杂类型中提取我们需要的部分。
基本语法
// 基本模式:T extends SomeType<infer U> ? U : never
type GetArrayType<T> = T extends (infer U)[] ? U : never
type StringArray = GetArrayType<string[]> // string
type NumberArray = GetArrayType<number[]> // number
type NotArray = GetArrayType<string> // never
// 提取第一个数组第一个类型
type First<T extends unknown[]> = T extends [infer A, ...unknown[]] ? A : never// 基本模式:T extends SomeType<infer U> ? U : never
type GetArrayType<T> = T extends (infer U)[] ? U : never
type StringArray = GetArrayType<string[]> // string
type NumberArray = GetArrayType<number[]> // number
type NotArray = GetArrayType<string> // never
// 提取第一个数组第一个类型
type First<T extends unknown[]> = T extends [infer A, ...unknown[]] ? A : never逆变和协变
协变,子类型可以赋值给父类型
interface Person {
name: string
}
interface Son {
name: string
hobbies: string[]
}
let person: Person = {
name: ''
}
let son: Son = {
name: '111',
hobbies: ['play game', 'writing']
}
person = son // ✅
son = person // ❌interface Person {
name: string
}
interface Son {
name: string
hobbies: string[]
}
let person: Person = {
name: ''
}
let son: Son = {
name: '111',
hobbies: ['play game', 'writing']
}
person = son // ✅
son = person // ❌函数的参数有逆变的性质,而返回值是协变的,也就是返回值的子类型可以赋值给父类型
interface Person {
name: string
}
interface Son {
name: string
hobbies: string[]
}
let printHobbies: (son: Son) => void
printHobbies = (son) => {
console.log(son.hobbies)
}
let printName: (person: Person) => void
printName = (person) => {
console.log(person.name)
}
// 父类型参数赋值给子
printHobbies = printName // ✅
printName = printHobbies // ❌interface Person {
name: string
}
interface Son {
name: string
hobbies: string[]
}
let printHobbies: (son: Son) => void
printHobbies = (son) => {
console.log(son.hobbies)
}
let printName: (person: Person) => void
printName = (person) => {
console.log(person.name)
}
// 父类型参数赋值给子
printHobbies = printName // ✅
printName = printHobbies // ❌函数的参数有逆变(父类型赋值给子类型)的性质
- 这是函数类型,函数的参数有逆变的特性,所以父类型赋值给子类型是允许的
- 下面是函数参数 hello 子类型赋值给父类型所以报错
- 返回值的位置是协变的,也就是赋值的函数的返回值是被赋值的函数的返回值的子类型
// 前置只是 undefined 是 void子类型
type A = undefined
type B = void
type C = A extends B ? true : false // true
type D = B extends A ? true : false // false
//
type Func = (a: string) => void
// func 会报错
// 不能将类型“(a: "hello") => undefined”分配给类型“Func”。
// 参数“a”和“a” 的类型不兼容。
// 不能将类型“string”分配给类型“"hello"”
const func: Func = (a: 'hello') => undefined// 前置只是 undefined 是 void子类型
type A = undefined
type B = void
type C = A extends B ? true : false // true
type D = B extends A ? true : false // false
//
type Func = (a: string) => void
// func 会报错
// 不能将类型“(a: "hello") => undefined”分配给类型“Func”。
// 参数“a”和“a” 的类型不兼容。
// 不能将类型“string”分配给类型“"hello"”
const func: Func = (a: 'hello') => undefined手写内置工具类型
手写 Awaited
type MyAwaited<T> = T extends Promise<infer Value> ? Value : never
type A = MyAwaited<Promise<string>>type MyAwaited<T> = T extends Promise<infer Value> ? Value : never
type A = MyAwaited<Promise<string>>手写 Partial
type MyPartial<T> = {
[Key in keyof T]?: T[Key]
}
interface Todo {
title: string
description: string
}
type A = MyPartial<Todo>type MyPartial<T> = {
[Key in keyof T]?: T[Key]
}
interface Todo {
title: string
description: string
}
type A = MyPartial<Todo>手写 Required
type MyRequired<T> = {
[Key in keyof T]-?: T[Key]
}
interface Props {
a?: number
b?: string
}
// 报错
const obj2: MyRequired<Props> = { a: 5 }type MyRequired<T> = {
[Key in keyof T]-?: T[Key]
}
interface Props {
a?: number
b?: string
}
// 报错
const obj2: MyRequired<Props> = { a: 5 }手写 Readonly
type MyReadonly<T> = {
readonly [Key in keyof T]: T[Key]
}
interface Todo {
title: string
}
const todo: MyReadonly<Todo> = {
title: 'Delete inactive users'
}
// 报错
todo.title = 'Hello'type MyReadonly<T> = {
readonly [Key in keyof T]: T[Key]
}
interface Todo {
title: string
}
const todo: MyReadonly<Todo> = {
title: 'Delete inactive users'
}
// 报错
todo.title = 'Hello'手写 Record
type MyRecord<T extends keyof any, K> = {
[P in T]: K
}
type CatName = 'miffy' | 'boris' | 'mordred'
interface CatInfo {
age: number
breed: string
}
const cats: MyRecord<CatName, CatInfo> = {
miffy: { age: 10, breed: 'Persian' },
boris: { age: 5, breed: 'Maine Coon' },
mordred: { age: 16, breed: 'British Shorthair' }
}
cats.boristype MyRecord<T extends keyof any, K> = {
[P in T]: K
}
type CatName = 'miffy' | 'boris' | 'mordred'
interface CatInfo {
age: number
breed: string
}
const cats: MyRecord<CatName, CatInfo> = {
miffy: { age: 10, breed: 'Persian' },
boris: { age: 5, breed: 'Maine Coon' },
mordred: { age: 16, breed: 'British Shorthair' }
}
cats.boris手写 Pick
// 遍历联合类型用in
type MyPick<T, K extends keyof T> = {
[Key in K]: T[Key]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false
}// 遍历联合类型用in
type MyPick<T, K extends keyof T> = {
[Key in K]: T[Key]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false
}手写 Omit
type MyOmit<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? never : Key]: T[Key]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false
}type MyOmit<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? never : Key]: T[Key]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false
}手写 Exclude
type MyExclude<T, U> = T extends U ? never : T
type ExcludeResult = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
// 解释
// 当 T 是联合类型时,TypeScript 会自动对每个成员进行分发操作:
// 分发到每个联合成员
('a' extends 'a' ? never : 'a') |
('b' extends 'a' ? never : 'b') |
('c' extends 'a' ? never : 'c')
// 计算结果
never | 'b' | 'c'
// never 在联合类型中被忽略
'b' | 'c'type MyExclude<T, U> = T extends U ? never : T
type ExcludeResult = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
// 解释
// 当 T 是联合类型时,TypeScript 会自动对每个成员进行分发操作:
// 分发到每个联合成员
('a' extends 'a' ? never : 'a') |
('b' extends 'a' ? never : 'b') |
('c' extends 'a' ? never : 'c')
// 计算结果
never | 'b' | 'c'
// never 在联合类型中被忽略
'b' | 'c'手写 Extract
type MyExtract<T, U> = T extends U ? T : never
type T0 = MyExtract<'a' | 'b' | 'c', 'a' | 'f'> // atype MyExtract<T, U> = T extends U ? T : never
type T0 = MyExtract<'a' | 'b' | 'c', 'a' | 'f'> // a手写 Parameters
type MyParameters<T extends (...args: any[]) => any> = T extends (...any: infer S) => any ? S : any
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]type MyParameters<T extends (...args: any[]) => any> = T extends (...any: infer S) => any ? S : any
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]手写 ReturnType
type MyReturnType<T extends Function> = T extends (...args: any) => infer Value ? Value : never
const fn = (v: boolean) => {
if (v) return 1
else return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"type MyReturnType<T extends Function> = T extends (...args: any) => infer Value ? Value : never
const fn = (v: boolean) => {
if (v) return 1
else return 2
}
type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"实现 isEqual
// 如果是普通方法
type SimpleEqual<T, U> = T extends U ? (U extends T ? true : false) : false
type Test = SimpleEqual<string, any> // boolean 而不是 false,因为受 any 影响转成了联合类型
// 更好的方案
type IsEqual<T, U> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? true : false
type Test = IsEqual<string, any> // false 而不是true// 如果是普通方法
type SimpleEqual<T, U> = T extends U ? (U extends T ? true : false) : false
type Test = SimpleEqual<string, any> // boolean 而不是 false,因为受 any 影响转成了联合类型
// 更好的方案
type IsEqual<T, U> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? true : false
type Test = IsEqual<string, any> // false 而不是true类型体操练习
总结
- TS静态类型检查,在编辑器中提前发现错误,减少生产环境问题,但是只做业务的话还是不要用太深入,权衡开发成本和维护成本
- 为什么 TS 类型编程可以叫类型体操,因为它是图灵完备的,递归、条件判断、循环(映射类型)、变量(泛型),甚至能实现 加减乘除运算