相关文章推荐
悲伤的鸭蛋  ·  antd 里常用的 TypeScript ...·  2 周前    · 
安静的饭盒  ·  Typescript: ...·  2 周前    · 
道上混的紫菜汤  ·  filebeat + logstash ...·  3 月前    · 
性感的炒饭  ·  QML Image Element ...·  1 年前    · 
玉树临风的钢笔  ·  JNBridge JMS Adapter ...·  1 年前    · 

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天, 点击查看活动详情

在学习typescript的过程当中,有一个github库对其类型的学习特别有帮助,是一个有点类似于leetcode的刷题项目,能够在里面刷各种关于typescript类型的题目,在上一篇文章中,我们完成了中等的第十七题,今天来做中等的第十八题 298-medium-length-of-string

下面这个是类型体操github仓库:

type-challenges/type-challenges: Collection of TypeScript type challenges with online judge (github.com)

298-medium-length-of-string

import type { Equal, Expect } from '@type-challenges/utils'
type cases = [
  Expect<Equal<LengthOfString<''>, 0>>,
  Expect<Equal<LengthOfString<'kumiko'>, 6>>,
  Expect<Equal<LengthOfString<'reina'>, 5>>,
  Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>,

从README和测试用例中能够得出,我们需要实现一个工具函数 LengthOfString 能够获取到字符串类型的长度。

利用 JS 进行模拟学习

对于 JS 来说,字符串属性上存在着一个 length 属性能够直接获取到字符串的长度,那么在 TS 中是不是也可以这样获取呢,答案是不可以的,不然这道题也就没有出现的必要了。至于为什么不可以,这个在后面 TS 部分再来讲解。

那么我们需要怎么去获取字符串的长度呢?

我们之前做过获取数组的长度,就可以直接通过 length 属性来获取,那么我们是不是可以进行字符串分割,把字符串的每一项都丢入一个数组,最后返回这个数组的长度。

function lengthOfString(str: string) {
    let strArr = []
    while(str != ''){
        strArr.push(str[0])
        str = str.slice(1)
	return strArr.length;

实现 Permutation

字符串和数组的区别

上面说到了对于 TS 中的 string 类型,没有办法直接通过 length 属性来获取长度,那么会得到什么?我们都知道 TS 是 JS 的超集,不存在某个属性 JS 能够获取,但是 TS 没办法获取。

type LengthOfS2 = 'asd'['length']
// type LengthOfS2 = number

我们会发现拿到的是 number 类型,而且不是具体的值

type LengthOfS3 = keyof string;
// type LengthOfS3 = number | typeof Symbol.iterator | "length" | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | ... 36 more ... | "at"

并且在我们打印出 字符串 的属性的时候,会发现确实存在一个 number 类型。

那么为什么数组就能够获取到具体的值呢?

type LengthOfS4 = {[P in keyof []]:[][P]};
// type LengthOfS4 = {
//     [x: number]: never;
//     length: 0;
//     toString: () => string;
//     toLocaleString: () => string;
//     pop: () => undefined;
//     push: (...items: never[]) => number;
//     concat: {
//         (...items: ConcatArray<never>[]): never[];
//         (...items: ConcatArray<...>[]): never[];
//     };
//     ... 28 more ...;
//     at: (index: number) => undefined;

同样的获取到数组对象的属性,我们就能够发现他们两个的不同,这也就是数组和字符串本质上的不同,数组是一个对象,但是字符串是基本数据类型。

那么字符串没有办法拿到长度,我们就可以按照上面 JS 的那种思路,先将他转换为数组,然后就可以通过获取数组长度来获取字符串长度了。

那么对于要把字符串转为数组,最简单能够想到的就是将字符串的首位或者尾位一个个取出来加入一个数组当中,在 TS 的泛型里面,我们要定义一个数组来进行存放,那就只能是定义在类型参数当中,可以给出一个默认的空值,在之后的递归当中不断地往里面添加属性。

type LengthOfString<S extends string, T extends any[] = []>

然后按照递归的边界条件来看,最极端的情况下就是当字符串已经为空了,那么就应该是要返回长度了,因为这个时候我们已经把字符串都拆分完毕了。

type LengthOfString<S extends string, T extends any[] = []> = S extends ""
	? T["length"]
	: never;

然后如果字符串不为空,按照我们上面的思路,就需要进行递归,去除字符串的第一个和剩下的字符串,剩下的字符串作为泛型的 S 入参,第一位则加入泛型的第二位入参。

去除第一位和剩余字符串的操作使用的就是 模板字面量 加上 infer 关键字

type LengthOfString<S extends string, T extends any[] = []> = S extends ""
	? T["length"]
	: S extends `${infer Start}${infer Rest}` ? LengthOfString<Rest,[Start,...T]> : never;

这样到这里,我们的测试就能够通过了,我们也能够成功的使用这个工具类型来获取字符串字面量的长度了。

type LengthOfS1 = LengthOfString<"asd">;
// type LengthOfS1 = 3

关于上述提到了部分的知识点:

  • Rest 参数
  • infer 关键字
  • 模板字面量
  • 使用入参加递归来定义参数
  • 今天这道题比较新颖的使用方法就是,泛型可以通过一个默认的入参来进行变量的保存,可以在他里面添加属性或者只是做一个备份之类的都很好用,有点类似于 JS 中函数里面的在函数一开始的时候生命一个变量这种用法。

    今天我们做完了中等的第十八题,题目属于比较常规的递归能够解决的问题,比较难想到的可能是通过入参来保存一个数组这种做法,但是由于 TS 的泛型中是没有办法定义变量的,属于大部分情况下我们都会这样来进行一个保存。