Ts 体操类型
前置知识
keyof
操作符:使用 keyof 操作符可以获取一个对象类型的所有 key 或 数字字面量组合的联合类型。
type Person = {
name: string
age: number
heigh: number
weigh: number
}
type Keys = keyof Person // 'name' | 'age' | 'heigh' | 'weigh'
typeof
操作符:使用 typeof 操作符可以获取一个值的类型。
type Person = {
name: string
age: number
heigh: number
weigh: number
}
type PersonType = typeof Person // { name: string, age: number, heigh: number, weigh: number }
- 下标操作符:使用下标操作符可以获取一个类型的索引类型,使用关键字
keyof
。
type Person = {
name: string
age: number
heigh: number
weigh: number
isMain: boolean
}
type NameType = Person['name'] // string
// 获取一个对象的所有 key 的类型
type ValueType = Person[keyof Person] // string | number | boolean
- 条件类型:使用条件类型可以根据一个类型的特性来确定另一个类型,在 ts 在可以通过 extends 关键字来实现条件类型。
type Person = {
name: string
age: number
heigh: number
weigh: number
}
type IsPerson<T> = T extends Person ? true : false
- 约束条件类型:使用 extends 关键字可以约束一个类型的特性,例如约束一个类型必须是一个对象类型。
type Person = {
name: string
age: number
heigh: number
weigh: number
}
type IsObject<T> = T extends object ? true : false
type IsPerson = IsObject<Person> // true
- 在条件类型中使用 infer 关键字进行类型推断操作: infer 关键字可以在条件类型中推断出一个类型。
// 获取返回值类型
type GetReturnType<T> = T extends (...args: never[]) => infer Return
? Return
: never
type Fn = () => string
type FnReturnType = GetReturnType<Fn> // string
- 映射类型:使用映射类型可以将一个类型的所有属性转换成另一个类型,使用关键字
in
。- 在属性修饰符前添加
-
或者+
前缀,再结合映射类型,可以实现对类型属性修饰符的修改操作。 -
操作符可以移除属性修饰符、+
操作符可以实现添加属性修饰符操作
- 在属性修饰符前添加
// 将一个类型的所有属性转换成另一个布尔类型
type TurnBoolean<T> = {
[Key in keyof T]: boolean
}
type Person = {
name: string
age: number
heigh: number
}
type PersonBoolean = TurnBoolean<Person> // { name: boolean, age: boolean, heigh: boolean }
type CreateMutable<T> = {
-readonly [Property in keyof T]: T[Property]
}
type LockedAccount = {
readonly id: string
readonly name: string
}
type UnlockedAccount = CreateMutable<LockedAccount> // { id: string, name: string }
// === 移除属性修饰符 ===
type Concrete<T> = {
[Property in keyof T]-?: T[Property]
}
type MaybeUser = {
id: string
name?: string
age?: number
}
type User = Concrete<MaybeUser> // { id: string, name: string, age: number }
- 映射类型结合 as 关键字实现对属性重命名: 使用 as 关键字可以实现对属性重命名操作。
type GettersPrefix<T> = {
[Property in keyof T as `get_${string & Property}`]: () => T[Property]
};
type Person {
name: string
age: number
heigh: number
}
type PersonGetters = GettersPrefix<Person> // { get_name: () => string, get_age: () => number, get_heigh: () => number }
- 映射类型结合条件类型操作符: 映射类型可以结合条件类型实现一些更高阶的操作。下面 ExtractPII 实现了通过条件类型,更改特定属性值类型的操作:。
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false
}
type DBFields = {
id: { format: 'incrementing' }
name: { type: string; pii: true }
}
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields> // { id: false, name: true }
- 模板字符串类型: 使用模板字符串类型可以实现对字符串的拼接操作。
type Person = { name: string; age: number; address: string }
// 全部重命名
type RenamePerson<T> = {
[K in keyof T as `rename_${K & string}`]: T[K]
}
// 仅为指定的key重命名
type RenamePersonForKey<T, X extends keyof T> = {
[K in keyof T as K extends X ? `rename_${K & string}` : K]: T[K]
}
type RenamedPerson = RenamePerson<Person> // { rename_name: string, rename_age: number, rename_address: string }
type RenamedPersonForKey = RenamePersonForKey<Person, 'name' | 'age'> // { rename_name: string, rename_age: number, address: string }
字符串类型体操
- 字符串首字母大写 CapitalizeString
type CapitalizeString<S extends string> =
S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : S
type res = CapitalizeString<'hello'> // 'Hello'
type res2 = CapitalizeString<'hello world'> // 'Hello world'
- 获取字符串第一个字符 FirstChar
type FirstChar<S extends string> = S extends `${infer First}${infer Rest}`
? First
: never
type res = FirstChar<'hello'> // 'h'
- 获取字符串最后一个字符 LastChar
type LastChar<S extends string> = S extends `${infer First}${infer Rest}`
? LastChar<Rest>
: First
type res = LastChar<'hello'> // 'o'
- 字符串转元组 StringToTuple
type StringToTuple<S extends string> = S extends `${infer First}${infer Rest}`
? [First, ...StringToTuple<Rest>]
: []
type res = StringToTuple<'hello'> // ['h', 'e', 'l', 'l', 'o']
- 元组转字符串 TupleToString
type TupleToString<T extends any[]> = T extends [infer First, ...infer Rest]
? `${First}${TupleToString<Rest>}`
: ''
type res = TupleToString<['h', 'e', 'l', 'l', 'o']> // 'hello'
- 重复字符串 RepeatString
type RepeatString<S extends string, N extends number> = N extends 0
? ''
: S extends `${infer First}${infer Rest}`
? `${First}${RepeatString<S, N>}`
: ''
type res = RepeatString<'hello', 3> // 'hellohellohello'
- 字符串分割 SplitString
type SplitString<
S extends string,
D extends string
> = S extends `${infer First}${D}${infer Rest}`
? [First, ...SplitString<Rest, D>]
: [S]
type res = SplitString<'hello world', ' '> // ['hello', 'world']
- 获取字符串长度 LengthOfString
type LengthOfString<S extends string> = S extends `${infer First}${infer Rest}`
? 1 + LengthOfString<Rest>
: 0
type res = LengthOfString<'hello'> // 5
- 驼峰转为短横线隔开式 KebabCase
type KebabCase<S extends string> = S extends `${infer First}${infer Rest}`
? First extends Uppercase<First>
? `-${Lowercase<First>}${KebabCase<Rest>}`
: `${First}${KebabCase<Rest>}`
: ''
type res = KebabCase<'helloWorld'> // 'hello-world'
短横线隔开式转为驼峰 CamelCase
type CamelCase<S extends string> = S extends `${infer First}${infer Rest}` ? First extends '-' ? `${Uppercase<Rest>}` : `${First}${CamelCase<Rest>}` : '' type res = CamelCase<'hello-world'> // 'helloWorld'
字符串是否包含某个字符 Include
type Include<
S extends string,
C extends string
> = S extends `${infer First}${infer Rest}`
? First extends C
? true
: Include<Rest, C>
: false
type res = Include<'hello', 'e'> // true
- 去掉左右空格 Trim
type TrimLeft<S extends string> = S extends `${' ' | '\n' | '\t'}${infer Rest}`
? TrimLeft<Rest>
: S
type TrimRight<S extends string> = S extends `${infer Rest}${' ' | '\n' | '\t'}`
? TrimRight<Rest>
: S
type Trim<S extends string> = TrimLeft<TrimRight<S>>
type res = Trim<' hello '> // 'hello'
- 字符串替换 Replace
type Replace<
T extends string,
C extends string,
RC extends string,
F extends string = ''
> =
// 空格替换 特殊处理
C extends ''
? T extends ''
? RC
: `${RC}${T}`
: T extends `${infer L}${C}${infer R}` // 匹配模式
? Replace<R, C, RC, `${F}${L}${RC}`> // 结果拼接并替换
: `${F}${T}`
type res = Replace<'hello', 'e', 'a'> // 'hallo'
- 函数重命名改变返回值类型 ComponentEmitsType
// 转化为
/*
{
onHandleOpen?: (flag: boolean) => void,
onPreviewItem?: (data: { item: any, index: number }) => void,
onCloseItem?: (data: { item: any, index: number }) => void,
}
*/
type a1 = {
'handle-open': (flag: boolean) => true
'preview-item': (data: { item: any; index: number }) => true
'close-item': (data: { item: any; index: number }) => true
}
type CamelCase<
T extends string,
F extends string = ''
> = T extends `${infer L}-${infer R1}${infer R2}`
? CamelCase<R2, `${F}${L}${Capitalize<R1>}`> // 递归R2,去掉-,拼接大写的R1
: Capitalize<`${F}${T}`> // 结果首字母也需要大写
type ComponentEmitsType<T> = {
[K in keyof T as `on${CamelCase<K & string>}`]: T[K] extends (
...args: infer P
) => any // 参数类型不变
? (...args: P) => void // 仅改变返回值类型
: T[K]
}
type a2 = ComponentEmitsType<a1>
type-challenges
开始
- Hello World
type HelloWorld = string
// you should make this work
type test = Expect<Equal<HelloWorld, string>>
简单
- Pick
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
// you should make this work
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
- Readonly
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: 'Hey',
description: 'foobar',
}
todo.title = 'Hello' // Error: cannot reassign a readonly property
todo.description = 'barFoo' // Error: cannot reassign a readonly property
- Tuple to Object
type TupleToObject<T extends readonly any[]> = {
[P in T[number]]: P
}
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
- First of Array
type First<T extends any[]> = T extends [] ? never : T[0]
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
- Length of Tuple
type Length<T extends readonly any[]> = T['length']
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = [
'FALCON 9',
'FALCON HEAVY',
'DRAGON',
'STARSHIP',
'HUMAN SPACEFLIGHT'
]
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
- Exclude
type MyExclude<T, U> = T extends U ? never : T
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
- Awaited
type MyAwaited<T> = T extends Promise<infer R> ? R : never
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
- If
type If<C extends boolean, T, F> = C extends true ? T : F
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
- Concat
type Concat<T extends any[], U extends any[]> = [...T, ...U]
type Result = Concat<[1], [2]> // expected to be [1, 2]
- Includes
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
- Push
type Push<T extends any[], U> = [...T, U]
type Result = Push<[1, 2], '3'> // [1, 2, '3']
- Unshift
type Unshift<T extends any[], U> = [U, ...T]
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
- Parameters 获取函数参数类型
type MyParameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
中级
- Get Return Type 函数返回值
type MyReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: never
const fn = (v: boolean) => {
if (v) return 1
else return 2
}
type a = MyReturnType<typeof fn> // should be "1 | 2"
- Omit 从对象中移除某些属性
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
- Readonly2
interface Todo {
title: string
description: string
completed: boolean
}
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P]
} & {
[P in Exclude<keyof T, K>]: T[P]
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: 'Hey',
description: 'foobar',
completed: false,
}
todo.title = 'Hello' // Error: cannot reassign a readonly property
todo.description = 'barFoo' // Error: cannot reassign a readonly property
todo.completed = true // OK
- Deep Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
type Todo = DeepReadonly<X> // should be same as `Expected`
- Tuple to Union
type TupleToUnion<T extends any[]> = T[number]
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
- Chainable Options
type Chainable<T = {}> = {
option<K extends string, V>(key: K, value: V): Chainable<T & { [P in K]: V }>
get(): T
}
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
// expect the type of result to be:
interface Result {
foo: number
name: string
bar: {
value: string
}
}