1、CapitalizeString

首字母大写

  1. type a1 = CapitalizeString<'handler'> // Handler
  2. type a2 = CapitalizeString<'parent'> // Parent
  3. type a3 = CapitalizeString<233> // 233

实现:

  1. type CapitalizeString<T> = T extends `${infer L}${infer R}` ? `${Uppercase<L>}${R}` : T

小结:
本小题通过inferT通过字符串拼接成左右两部分;且L匹配左边第一个字符,R匹配剩下的字符。

参考:https://blog.gplane.win/posts/ts-template-literal-types.html

2、FirstChar

获取字符串字面量中的第一个字符

  1. type A = FirstChar<'BFE'> // 'B'
  2. type B = FirstChar<'dev'> // 'd'
  3. type C = FirstChar<''> // never

实现:

  1. type FirstChar<T> = T extends `${infer L}${infer R}` ? L : never

3、LastChar

获取字符串字面量中的最后一个字符

  1. type A = LastChar<'BFE'> // 'E'
  2. type B = LastChar<'dev'> // 'v'
  3. type C = LastChar<''> // never

实现:

  1. type LastChar<T> = T extends `${infer L}${infer S}` ? (S extends '' ? L : LastChar<S>) : never

小结:
每次infer L只从左匹配一个字符,想要获得字符串最后一个字符,就需要递归。截止条件就是剩余字符infer S''(空字符串)时,返回L,否则将S继续向下递归。

4、StringToTuple

字符串转换为元组类型

  1. type A = StringToTuple<'BFE.dev'> // ['B', 'F', 'E', '.', 'd', 'e', 'v']
  2. type B = StringToTuple<''> // []

实现:

  1. type StringToTuple<T extends string, K extends any[] = []> = T extends `${infer L}${infer R}` ? (L extends '' ? K : StringToTuple<R, [...K, L]>) : K

5、TupleToString

将字符串类型的元素转换为字符串字面量类型

  1. type A = TupleToString<['a', 'b', 'c']> // 'abc'
  2. type B = TupleToString<[]> // ''
  3. type C = TupleToString<['a']> // 'a'

实现:

  1. type TupleToString<T, S extends string = ''> = T extends [infer L, ...infer R] ? (
  2. L extends string ? TupleToString<R, `${S}${L}`> : never
  3. ) : S

小结:
在三元运算符中,对于LR而言,无法确定其类型,是无法直接使用的。

6、RepeatString

复制字符T为字符串类型,长度为C

  1. type A = RepeatString<'a', 3> // 'aaa'
  2. type B = RepeatString<'a', 0> // ''

实现:

  1. type RepeatString<T extends string, C extends number,
  2. K extends any[] = [], S extends string = ''> = C extends K['length'] ? S : RepeatString<T, C, [...K, T], `${S}${T}`>

小结:
TS中关于数字的比较都会用元组的length属性。

7、SplitString

将字符串字面量类型按照指定字符,分割为元组。无法分割则返回原字符串字面量

  1. type A1 = SplitString<'handle-open-flag', '-'> // ["handle", "open", "flag"]
  2. type A2 = SplitString<'open-flag', '-'> // ["open", "flag"]
  3. type A3 = SplitString<'handle.open.flag', '.'> // ["handle", "open", "flag"]
  4. type A4 = SplitString<'open.flag', '.'> // ["open", "flag"]
  5. type A5 = SplitString<'open.flag', '-'> // ["open.flag"]

实现:

  1. type SplitString<T extends string, P extends string,
  2. K extends string[] = []> = T extends `${infer L}${P}${infer R}` ? SplitString<R, P, [...K, L]> : [...K, T]

8、LengthOfString

计算字符串字面量类型的长度

  1. type A = LengthOfString<'BFE.dev'> // 7
  2. type B = LengthOfString<''> // 0

实现:

  1. type LengthOfString<T extends string, K extends any[] = []> = T extends `${infer L}${infer R}` ? LengthOfString<R, [...K, L]> : K['length']

9、KebabCase

驼峰命名转横杠命名

  1. type a1 = KebabCase<'HandleOpenFlag'> // handle-open-flag
  2. type a2 = KebabCase<'OpenFlag'> // open-flag

实现:

  1. type KebabCase<T, S extends string = ''> = T extends `${infer L}${infer R}` ? (
  2. L extends Uppercase<L> ? (S extends '' ? KebabCase<R, Lowercase<L>> : KebabCase<R, `${S}-${Lowercase<L>}`>) : KebabCase<R, `${S}${L}`>
  3. ) : S

10、CamelCase

横杠命名转化为驼峰命名

  1. type a1 = CamelCase<'handle-open-flag'> // HandleOpenFlag
  2. type a2 = CamelCase<'open-flag'> // OpenFlag

实现:

  1. type CapitalizeString<T> = T extends `${infer L}${infer R}` ? `${Uppercase<L>}${R}` : T
  2. type CamelCase<T extends string, S extends string = ''> = T extends `${infer L}-${infer R}` ? CamelCase<R, `${S}${CapitalizeString<L>}`> : `${S}${CapitalizeString<T>}`

11、ObjectAccessPaths

得到对象中的值访问字符串

  1. // 简单来说,就是根据如下对象类型:
  2. /*
  3. {
  4. home: {
  5. topBar: {
  6. title: '顶部标题',
  7. welcome: '欢迎登录'
  8. },
  9. bottomBar: {
  10. notes: 'XXX备案,归XXX所有',
  11. },
  12. },
  13. login: {
  14. username: '用户名',
  15. password: '密码'
  16. }
  17. }
  18. */
  19. // 得到联合类型:
  20. /*
  21. home.topBar.title | home.topBar.welcome | home.bottomBar.notes | login.username | login.password
  22. */
  23. // 完成 createI18n 函数中的 ObjectAccessPaths<Schema>,限制函数i18n的参数为合法的属性访问字符串
  24. function createI18n<Schema>(schema: Schema): ((path: ObjectAccessPaths<Schema>) => string) {return [{schema}] as any}
  25. // i18n函数的参数类型为:home.topBar.title | home.topBar.welcome | home.bottomBar.notes | login.username | login.password
  26. const i18n = createI18n({
  27. home: {
  28. topBar: {
  29. title: '顶部标题',
  30. welcome: '欢迎登录'
  31. },
  32. bottomBar: {
  33. notes: 'XXX备案,归XXX所有',
  34. },
  35. },
  36. login: {
  37. username: '用户名',
  38. password: '密码'
  39. }
  40. })
  41. i18n('home.topBar.title') // correct
  42. i18n('home.topBar.welcome') // correct
  43. i18n('home.bottomBar.notes') // correct
  44. // i18n('home.login.abc') // error,不存在的属性
  45. // i18n('home.topBar') // error,没有到最后一个属性

实现:

  1. type RemoveFirstDot<T> = T extends `.${infer L}` ? L : T
  2. type ObjectAccessPaths<T, P extends string = '', K = keyof T> = K extends keyof T ? (
  3. K extends string ? (
  4. T[K] extends Record<string, any> ? ObjectAccessPaths<T[K], `${P}.${K}`> : RemoveFirstDot<`${P}.${K}`>
  5. ) : never
  6. ) : never

12、ComponentEmitsType

定义组件的监听事件类型

  1. // 实现 ComponentEmitsType<Emits> 类型,将
  2. /*
  3. {
  4. 'handle-open': (flag: boolean) => true,
  5. 'preview-item': (data: { item: any, index: number }) => true,
  6. 'close-item': (data: { item: any, index: number }) => true,
  7. }
  8. */
  9. // 转化为类型
  10. /*
  11. {
  12. onHandleOpen?: (flag: boolean) => void,
  13. onPreviewItem?: (data: { item: any, index: number }) => void,
  14. onCloseItem?: (data: { item: any, index: number }) => void,
  15. }
  16. */
  17. function createComponent<Emits extends Record<string, any>>(emits: Emits): ComponentEmitsType<Emits> {return [{emits}] as any}
  18. // 最后返回的 Component变量类型为一个合法的React组件类型,并且能够通过`on事件驼峰命名`的方式,监听定义的事件,并且能够自动推导出事件的参数类型
  19. const Component = createComponent({
  20. 'handle-open': (flag: boolean) => true,
  21. 'preview-item': (data: { item: any, index: number }) => true,
  22. 'close-item': (data: { item: any, index: number }) => true,
  23. })
  24. console.log(
  25. <Component
  26. // onHandleOpen 的类型为 (flag: boolean) => void
  27. onHandleOpen={val => console.log(val.valueOf())}
  28. // onPreviewItem 的类型为 (data: { item: any, index: number }) => void
  29. onPreviewItem={val => {
  30. const {item, index} = val
  31. const a: number = item
  32. console.log(a, index.toFixed(2))
  33. }}
  34. // 所有的监听事件属性都是可选属性,可以不传处理函数句柄
  35. // onCloseItem={val => [{val}]}
  36. />
  37. )
  38. // 提示,定义组件的props类型方式为 { (props: Partial<Convert<Emits>>): any }
  39. // 比如 Comp 可以接收属性 {name:string, age:number, flag:boolean, id?:string},其中id为可选属性,那么可以这样写
  40. const Comp: { (props: { name: string, age: number, flag: boolean, id?: string }): any } = Function as any
  41. console.log(<Comp name="" age={1} flag/>) // 正确
  42. console.log(<Comp name="" age={1} flag id="111"/>) // 正确
  43. // console.log(<Comp name={1} age={1} flag/>) // 错误,name为字符串类型
  44. // console.log(<Comp age={1} flag/>) // 错误,缺少必须属性name:string

实现:

  1. type CapitalizeString<T> = T extends `${infer L}${infer R}` ? `${Uppercase<L>}${R}` : T
  2. type CamelCase<T extends string, S extends string = ''> = T extends `${infer L}-${infer R}` ? CamelCase<R, `${S}${CapitalizeString<L>}`> : `${S}${CapitalizeString<T>}`
  3. type ComponentEmitsType<T> = {
  4. [key in keyof T as `on${key extends string ? CamelCase<key> : ''}`]?: T[key] extends ((...args: infer A) => any) ? (...args: A) => void : T[key]
  5. }