TS试炼-5
摘要:
包含:Remove Index Signature(去除签名索引)、Percentage Parser(百分比解析)、Drop Char(剔除字符)、MinusOne(正整数减一)、PickByType(根据类型摘取)、StartsWith(从开始)、EndsWith(以结束)、PartialByKeys(指定属性可选)、RequiredByKeys(指定属性必选)、Mutable(去除只读)、OmitByType(根据类型剔除属性)
1、Remove Index Signature 去除签名索引
ts
const foobar = Symbol('foobar')
type Foo = {
[key: string]: any
foo(): void
[key: number]: any
0: string
[key: symbol]: any
[foobar](): void
}
// 去除签名索引,转换后结果为
type Result = {
0: string
foo(): void
[foobar](): void
}
第一种方法
ts
type RemoveIndexSignature<T> = {
[K in keyof T as
string extends K ? never :
number extends K ? never :
symbol extends K ? never :
K
]: T[K];
};
解释:
1、K 在 keyof T 里,所以将对T的所有属性操作
2、第一步:此时K为string
,判断string是否继承自string
,满足条件,直接never
3、第二步:此时K为foo()
,判断string是否继承自foo()
,不满足,下一步,往后均不满足,最终foo()保留
4、后续同上,只是number在第二个条件剔除,symbol在第三个条件剔除。
第二种方法
ts
type RemoveIndexSignature<T, P = PropertyKey> = {
[K in keyof T as P extends K ? never : K extends P ? K : never]: T[K];
};
解释:
上面方法的简写 /* P = string | number | symbol */
1、第一步,此时K为string
,因为P是联合类型,将做类似js循环的操作,string继承自string
,满足直接never,number和symbol不继承自string
,进行下一步判断,string
不继自承number和symbol,最终为never
2、第二步,此时K为foo()
,string、number、symbol均不继承自foo()
,都到下一步判断,其中foo()
继承自string,最终返回foo()
3、后续同上
2、Percentage Parser 百分比解析
ts
'' => ['', '', '']
'123' => ['', '123', '']
'+123' => ['+', '123', '']
'-123' => ['-', '123', '']
'123%' => ['', '123', '%']
'-123%' => ['-', '123', '%']
'%' => ['', '', '%']
方法一,使用第二个变量存储
ts
type PercentageParser<
A extends string,
R extends string[] = ['', '', ''],
> = A extends `${infer F extends '+' | '-'}${infer rest}`
? PercentageParser<rest, [F, '', '']>
: A extends `${infer F}%`
? [R[0], F, '%']
: [R[0], A, ''];
方法二,不使用第二个变量
ts
type CheckPrefix<T> = T extends '+' | '-' ? T : never
type CheckSuffix<T> = T extends `${infer rest}%` ? [rest, '%'] : [T, '']
type PercentageParser<A extends string> = A extends `${CheckPrefix<infer F>}${infer rest}` ? [F, ...CheckSuffix<rest>] : ['', ...CheckSuffix<A>]
3、Drop Char 剔除字符
ts
('butter fly!', ' ') => 'butterfly!'
(' but ter fly ! ', ' ') => 'butterfly!'
(' b u t t e r f l y ! ', 't') => ' b u e r f l y ! '
基本实现
ts
type DropChar<S, C extends string> = S extends `${infer L}${C}${infer R}` ? DropChar<`${L}${R}`, C> : S;
防止无限递归
ts
type DropChar<S, C extends string> = C extends ''
? S
: S extends `${infer F}${C}${infer L}`
? DropChar<`${F}${L}`, C>
: S;
4、MinusOne 正整数减一
ts
1 => 0
100 => 99
9_007_199_254_740_992 => 9_007_199_254_740_991
github大佬答案
ts
type ParseInt<T extends string> = T extends `${infer Digit extends number}` ? Digit : never
type ReverseString<S extends string> = S extends `${infer First}${infer Rest}` ? `${ReverseString<Rest>}${First}` : ''
type RemoveLeadingZeros<S extends string> = S extends '0' ? S : S extends `${'0'}${infer R}` ? RemoveLeadingZeros<R> : S
type InternalMinusOne<
S extends string
> = S extends `${infer Digit extends number}${infer Rest}` ?
Digit extends 0 ?
`9${InternalMinusOne<Rest>}` :
`${[9, 0, 1, 2, 3, 4, 5, 6, 7, 8][Digit]}${Rest}`: // 此处第一个9可以为任何值,因为永远取不到
never
type MinusOne<T extends number> = ParseInt<RemoveLeadingZeros<ReverseString<InternalMinusOne<ReverseString<`${T}`>>>>>
解析:
通过翻转后,对第一个数减一,再翻转恢复。如果翻转后的第一个数为0,则变为9,并对剩余位数值减一。
这里之所以选择翻转,是因为字符串使用infer推导时,没有特殊间隔只会取出第一个及后续,无法直接取出最后一个和前面的。
ts
type A = 100;
// "001"
type B = ReverseString<`${A}`>;
// "990"
type C = InternalMinusOne<B>;
// "099"
type D = ReverseString<C>;
// "99"
type E = RemoveLeadingZeros<D>;
// 99
type F = ParseInt<E>;
不使用翻转,采用数组
ts
type ParseInt<T extends string> = T extends `${infer Digit extends number}` ? Digit : never;
type StringToArray<T extends string> = T extends `${infer R extends number}${infer U}` ? [R, ...StringToArray<U>] : [];
type ArrayToString<T extends number[]> = T extends [infer R extends number, ...infer U extends number[]] ? `${R}${ArrayToString<U>}` : '';
type RemoveLeadingZeros<S extends string> = S extends '0' ? S : S extends `${'0'}${infer R}` ? RemoveLeadingZeros<R> : S;
type InternalMinusOne<S extends number[]> = S extends [
...infer Rest extends number[],
infer Digit extends number,
]
? Digit extends 0
? `${InternalMinusOne<Rest>}9`
: `${ArrayToString<Rest>}${[9, 0, 1, 2, 3, 4, 5, 6, 7, 8][Digit]}` // 此处第一个9可以为任何值,因为永远取不到
: never;
type MinusOne<T extends number> = ParseInt<RemoveLeadingZeros<InternalMinusOne<StringToArray<`${T}`>>>>;
解析:
核心方法一样,只是数组可以直接取最后一个值,就不需要翻转了,但是也多了转数组和数组转字符串的方法。
扩展,支持所有整数
ts
type ParseInt<T extends string> = T extends `${infer Digit extends number}`? Digit: never
type InternalMinusOne<T extends string> = T extends `${infer Digit extends number}${infer Remain}`?
Digit extends 0? `9${InternalMinusOne<Remain>}`: `${[9,0,1,2,3,4,5,6,7,8][Digit]}${Remain}`:
never
type InternalPlusOne<T extends string> = T extends `${infer Digit extends number}${infer Remain}`?
Digit extends 9? `0${InternalPlusOne<Remain>}`: `${[1,2,3,4,5,6,7,8,9,0][Digit]}${Remain}`:
never
type RemoveZeroPrefix<T extends string> = T extends '0'? T: T extends `0${infer Remain}`? RemoveZeroPrefix<Remain>: T
type Reverse<T extends string> = T extends `${infer F}${infer R}`? `${Reverse<R>}${F}`: T
type MinusOne<T extends number> = T extends 0?
-1 :
`${T}` extends `-${infer Abs}`?
ParseInt<`-${RemoveZeroPrefix<Reverse<InternalPlusOne<Reverse<Abs>>>>}`>:
ParseInt<RemoveZeroPrefix<Reverse<InternalMinusOne<Reverse<`${T}`>>>>>
5、PickByType 根据类型摘取
ts
type OnlyBoolean = PickByType<{
name: string
count: number
isReadonly: boolean
isEnable: boolean
}, boolean> // { isReadonly: boolean; isEnable: boolean; }
实现
ts
type PickByType<T, U> = { [P in keyof T as T[P] extends U ? P : never]: T[P] };
type PickByType<T, U> = Pick<T, { [K in keyof T]: T[K] extends U ? K : never }[keyof T]>;
6、StartsWith 从开始 | EndsWith 以结束
ts
type a = StartsWith<'abc', 'ac'> // expected to be false
type b = StartsWith<'abc', 'ab'> // expected to be true
type c = StartsWith<'abc', 'abcd'> // expected to be false
type d = EndsWith<'abc', 'bc'> // expected to be true
type e = EndsWith<'abc', 'abc'> // expected to be true
type f = EndsWith<'abc', 'd'> // expected to be false
实现
ts
type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false
type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false
7、PartialByKeys 指定属性可选
ts
interface User {
name: string
age: number
address: string
}
type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; age:number; address:string }
实现
ts
type PartialByKeys<T, K extends keyof T = keyof T> = Omit<Partial<Pick<T, K>> & Omit<T, K>, never>;
说明:
因题目验证限制需要使用Omit<xxx, never>,实际不使用Omit在使用效果相同,只是无法过题目验证。
不使用内置方法
ts
type _Clone<T> = {
[P in keyof T]: T[P];
};
type PartialByKeys<T, K extends keyof T = keyof T> = _Clone<
{
[P in K]?: T[P];
} & {
[P in keyof T as P extends K ? never : P]: T[P];
}
>;
8、RequiredByKeys 指定属性必选
ts
interface User {
name?: string
age?: number
address?: string
}
type UserRequiredName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
基本实现
ts
type RequiredByKeys<T, K extends keyof T = keyof T> = Omit<Required<Pick<T, K>> & Omit<T, K>, never>
不使用内置方法
ts
type _Clone<T> = {
[P in keyof T]: T[P];
};
type RequiredByKeys<T, K extends keyof T = keyof T> = _Clone<{
[P in K]-?: T[P]
} & {
[P in keyof T as P extends K ? never : P]?: T[P]
}>
9、Mutable 去除只读
ts
interface Todo {
readonly title: string
readonly description: string
readonly completed: boolean
}
type MutableTodo = Mutable<Todo> // { title: string; description: string; completed: boolean; }
实现
ts
type Mutable<T extends object> = {
-readonly [P in keyof T]: T[P]
}
10、OmitByType 根据类型剔除属性
ts
type OmitBoolean = OmitByType<{
name: string
count: number
isReadonly: boolean
isEnable: boolean
}, boolean> // { name: string; count: number }
实现
ts
type OmitByType<T, U> = { [P in keyof T as T[P] extends U ? never : P]: T[P] }
type OmitByType<T, U> = Pick<T, { [K in keyof T]: T[K] extends U ? never : K }[keyof T]>
评论
0条评论
暂无内容,去看看其他的吧~