2024年前端最新成为优秀的TS体操高手 之 TS 类型体操前置知识储备_ts类型体操,面试总结报告怎么写

最后

中年危机是真实存在的,即便有技术傍身,还是难免对自己的生存能力产生质疑和焦虑,这些年职业发展,一直在寻求消除焦虑的依靠。

  • 技术要深入到什么程度?

  • 做久了技术总要转型管理?

  • 我能做什么,我想做什么?

  • 一技之长,就是深耕你的专业技能,你的专业技术。(重点)

  • 独立做事,当你的一技之长达到一定深度的时候,需要开始思考如何独立做事。(创业)

  • 拥有事业,选择一份使命,带领团队实现它。(创业)

一技之长分五个层次

  • 栈内技术 - 是指你的前端专业领域技术

  • 栈外技术 - 是指栈内技术的上下游,领域外的相关专业知识

  • 工程经验 - 是建设专业技术体系的“解决方案”

  • 带人做事 - 是对团队协作能力的要求

  • 业界发声 - 工作经验总结对外分享,与他人交流

永远不要放弃一技之长,它值得你长期信仰持有

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue 等等。

/**
 * From T, pick a set of properties whose keys are in the union K
 */type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};interface Todo {  title: string;
  description: string;
  completed: boolean;
}type TodoPreview = Pick<Todo, "title" | "completed">;const todo: TodoPreview = {  title: "Clean room",  completed: false,
};

extends 上面讲过,属于范畴判断/约束类型,在泛型定义<>里面明显就是为了约束类型

keyof 的作用可以理解为 把一个对象中的所有  提取出来。

  • 直接用 keyof 提取一个对象看看效果:
type TodoKeys = keyof Todo//type TodoKeys =  'title' | 'description' | 'completed'type testkeys = keyof {[k:number]: any; name:string}// type testkeys = number | "name"// 特殊的例子type Mapish = { [k: string]: boolean; name:string };type M = keyof Mapish;// type M = string | number

上面的几个例子中,第一个是最好理解的,提取所有的 

第二个案例中,k 作为未知的内容,提取 number 作为 key 的范围,加上 “name” 所以就得出 number | "name"

特殊的例子也是官网的例子,为啥会有个 number 类型?

原话:Note that in this example, M is string | number — this is because JavaScript object keys are always coerced to a string, so obj[0] is always the same as obj[“0”].
翻译:请注意,在此示例中,M string | number — 这是因为 JavaScript 对象键总是被强制转换为字符串,所以 obj[0] 总是与 obj[“0”] 相同

结合 demo ,K extends keyof T 意思也就是说,K 参数的取值范围只能在 Todo 的键中取('title' | 'description' | 'completed'),限制为字符串,并且是这 3 个键中的其中一个/多个,只能少,不能多

如果是下面这种

type Mapish = { [k: string]: boolean; name:string };// K extends keyof Mapish;

K 的范围则是 string | number 类型(因为没有具体的键名,所以只能不限制具体的键名,只限制类型

像这种用 | 拼起来的(或类型)规范点叫做 unio 联合数据类型,想要循环这些数据,可以用到 in 关键字


回到 demo 的讲解

  • Pick 中 泛型 T 传入的是 Todo 类型
  • K extends keyof T :K 限制为 Todo 的键值(传入的是 “title” | “completed” 符合要求,因为只能少不能多嘛)
  • P in k 可以理解为 循环
  • P 会依次被赋值为 title
  • 然后赋值 completed
  • T[P] 的意思和 JS 的[]取值一样,获取 T[‘title’] => string 和 T[‘completed’] => completed
  • {} 会包裹循环出来的结果
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};type TodoPreview = Pick<Todo, "title" | "completed">;// TodoPreview = {title:string,completed:boolean}

Pick 的实现就完成了。[P in K] 这个循环和我们 JS 常规的写法很不一样,需要消化一下,其他应该都好理解


  • 以此内推,完成 Readonly

    • 众所周知 readonly 只需要在键值前面加上 readonly 参数即可

实现如下:keyof 写到了中括号里面,这个是允许的,这些关键字无论写在哪里只要符合规范都 OK,比如 MyReadonly2

type MyReadonly<T> = {  readonly [P in keyof T]: T[P]
}// 这个是画蛇添足的,因为P肯定是属于keyof T的,就是从keyof T中循环出来的// 不过证明keyof不仅仅在 <> 中能用type MyReadonly2<T> = {  readonly [P in keyof T]: P extends keyof T ?  T[P] : never}

小拓展: 'name' extends keyof T ? true : false也能判断 T 这个泛型对象中有没有 name 这个属性

keyof 和 in 小结
  • keyof 是为了拿到一个对象中的所有的键名,当键名是一个类型的时候,则会全部被升级为对应的 类型
  • in 则是为了循环 unio 联合类型数据的,多数都用于循环重新生成一个对象
JS 转 TS – typeof 关键字

typeof 作为从 js 世界转换为 ts 世界的内容。

假设一个场景,我们使用一个 obj 对象作为数据映射的存储,比如使用一个 map,存储状态码返回对应的 msg:

const statusMap = {  200: '操作成功',  404: '内容找不到',  500: '操作失败',  10001: '登录失效'}var status1 = 200console.log(statusMap[status1]) // 操作成功var status2 = 10001console.log(statusMap[status2]) // 登录失效

类似上面的场景,那这时候statusMap[] 中间的值肯定只有 200,404,500,10001 才符合要求,在 TS 层面自然我们就要约定 status 在这个范围中

如果不用 typeof ,我们可以会写出这样的 TS:

type Status = 200 | 404 | 500 | 10001var status1: Status = 200console.log(statusMap[status1]) // 操作成功var status2: Status = 10001console.log(statusMap[status2]) // 登录失效var status3: Status = 301 // 报红 (Type '301' is not assignable to type 'Status')

这时候假如我们在新增了几个键值,那 TS 还得在同步跟着改一次(太麻烦了),用上 typeof

const statusMap = {  200: '操作成功',  404: '内容找不到',  500: '操作失败',  10001: '登录失效'}type MyStatusMap = typeof statusMap// 这时候 MyStatus 的值会变成// type MyStatus = {//     200: string;//     404: string;//     500: string;//     10001: string;// }// 然后接上keyof关键字,提取对象的键名type MyStatus = keyof MyStatusMap// type MyStatus = 200 | 404 | 500 | 10001// 上面的2步可以简写一步到位type MyStatus2 = keyof typeof statusMap// type MyStatus2 = 200 | 404 | 500 | 10001

这样只需要我们改动 JS 的内容,TS 将会自动获取对新的对象。返回对象后还不够,因为我们上面想约束的是传入的键值(想获取键值,刚好上面学习了 keyof 关键字),就能动态获取所有符合规范的键值了!

typeof 小结

typeof 是一个可以动态把 JS 的对象转换为 TS 的关键字。

不过也有限制场景,那就是转换的前提是这部分 JS 是已固定的内容。就好比例子中的一个对象映射,那是固定的内容,然后让 TS 去推导

而且打包后的代码是不可能存在 TS 的,如果想实现后端接口动态返回内容在用 typeof ,这是实现不了的

数组和字符串的循环 推断类型 infer

infer 应用场景非常多

简单一句话概括 infer 只是一个 占位 的工具,我就站在这个位置,至于这个位置是什么内容 infer 并不关心,可以留给后面的程序去判断

用简单题的 00014-easy-first 来讲解一下。实现一个 First 工具类型

通常获取第一个元素,我们想到的就是 T[0] 当然在 TS 这个语法是可以行得通的

可以在测试用例中有一项

Equal<First<[]>, never> // 使用 T[0] 的话这个会报错,因为 First<[]> 返回的是 undefined

也就是说当这个元素非指定声明为 undefined 时,在 TS 多数都是要用 never 代替

正确答案如下:

type First<T extends any[]> = T extends [infer F,...infer Rest] ? F : never

简单的说一下

  • infer 必须在 TS 函数运算过程中使用(在定义泛型的<>中不能使用,而 extends 就可以)

  • infer 可以配合 … 进行运算

  • T extends [infer F,...infer Rest]

    • 表达的意思就是 T extends [F,…Rest 剩余的值]。T 肯定是存在一个属性F的数组,...Rest 是剩下的内容,可有可无
    • ...infer Rest 就是把除了 F 之外的元素在归集为一个数组(这个是 ES6 的知识了)
    • infer F 的意思就是,我拿 F 在这个数组里面 占位 ,数组的第一项的内容,就是被 F 占了
  • 回归到题目,我们要拿的也正是第一项,所以直接 return F 类型

  • 如果 T 是一个空数组,那么 extends 那一步就都判断不通过,自然返回的就是 never,符合测试用例的要求。

infer 的其他妙用

infer 还能遍历字符串

比如起一个 字符串切割为数组的需求:

type Split<T extends string, U extends unknown[] = []> =
  T extends `${infer F}${infer Rest}` ? Split<Rest, [...U, F]> : Utype testSplit = Split<'123456'>// type testSplit = ["1", "2", "3", "4", "5", "6"]

其中 ${infer F}${infer Rest} 的意思就是,F 占第一个字符,Rest 占剩下的字符,因为在字符串中不存在...的概念,所以 Rest 基本上就是占据了剩下的字符了

像这样的一个测试例子,一共有 3 个占位符,

type testInfer<T extends string> = T extends `${infer F}${infer S}${infer R}` ? [F, S, R] : Ttype testInfer1 = testInfer<'123456'>// 按照占位符的特性,前面F和S分别占据2个字符,剩余的都给R占去了// type testInfer1 = ["1", "2", "3456"]// 稍作改动,在S占位符后面添加一个5type testInfer2<T extends string> = T extends `${infer F}${infer S}5${infer R}` ? [F, S, R] : Ttype testInfer3 = testInfer<'123456'>// F 占第一个字符 = 1// S 占据2-4,因为在R之前有一个5,所以S代表了第二个字符开始到5的所有字符// 那么R就是从5开始,到末尾,所以得出的结果如下:// type testInfer1 = ["1", "234", "6"]

后面的习题还有很多会用到 infer ,所以先了解好 infer 占位 的特性就好

infer 小结

infer 相当于一个占位置的关键字,把占下来的位置复制给对应的运算变量。

其中对于数组或者其他的类型来说,还能用 … 把所有的位置归结起来形成一个数组

对于字符串这种不存在 … 拓展运算符的来说,只要前面占了一个位置,剩下的字符就会被第二个占位符全部代替

数组的用法

数组也是 TS 体操的一个很重要的特性,因为在 TS 体操中并没有加减法的概念,实际运算中少不了加减法的操作,包括获取长度之类的。

所以数组还充当了 计数器 的作用。关于数组的计数器还有一个非常有用的技巧,有用到我觉得可以单独再起一个文章细细分析,下面就先简单的介绍一下数组的功能

先来一道简单题,学会数组的基本属性和用法 00018-easy-tuple-length

题目给出 2 个数组,用了 as const。需要求出这 2 个数组的长度

const tesla = ['tesla', 'model 3', 'model X', 'model Y'] as constconst spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] as consttype teslaLength = Length<typeof tesla>type spaceXLength = Length<typeof spaceX>// 答案:type Length<T extends readonly any[]> = T['length']

是不是很简单?! T['length'] 完事~

所以数组一个很重要的特性就是,他有 length 属性。

想用 length 属性的前提: T extends any[] T 类型的范围,肯定是一个数组,any[] 或者 unknown[]。都行,反正是数组,就有 length 属性。


基于这个求长度的问题,延伸一下,求 2 个数组合并后的长度

type MergeArray<T extends readonly any[],U extends readonly any[]> = [...T,...U]['length']type arrLength = MergeArray<typeof tesla, typeof spaceX> // 9

稍微来点有意思的题目在感受一下数组的作用 难度为中等的题 05153-medium-indexof

实现一个 indexOf(原题还有另外一个需要注意的点就是判断类型的时候需要注意的地方,我后面还会起文章来讲解,现在先看最简单的实现):

既然要算索引位置,自然就涉及到了一个 计数器 的问题,看下面的答案

type numberIndex = IndexOf<[1, 2, 3], 1> // 需要得到的答案是 0type numberIndex2 = IndexOf<[1, 2, 3], 99> // 需要得到的答案是 -1type numberIndex3 = IndexOf<[1, 2, 3], 1> // 需要得到的答案是 0// 题目给出的初始模版(缺少计数器)// type IndexOf<T extends unknown[],U> = any// 对于这些缺参数的,我们完全可以自己补一个参数,而且补充默认值type IndexOf2<T extends unknown[], U, C extends 1[] = []> =         
  T extends [infer F, ...infer Rest] ?             
    (F extends U ? C['length'] : IndexOf<Rest, U, [...C, 1]>) : -1

讲解部分:

  • 因为题目给出的模版缺少了一个计数器,我们可以补充一个 C 变量,并且默认赋值为 []

    • 只要有默认值,就非必填,非必填的话测试用例就不会报错了
  • T extends 和 infer 部分上面有讲解过,如果 T 已经不满足至少有一个F的时候,说明 T 数组已经空了,空了之后就说明可以得出结果了

    • T 为空了,还没匹配到数据,按 indexOf 的方法应该是返回 -1
    • T 如果符合了至少有一个 F 变量的要求的话,判断F与传入的U数据相比,相同的话返回当前计数器的长度(也就是要计算的索引了) C['length']
    • 不符合的话,拿Rest 数组继续循环(递归调用 IndexOf),与此同时 递归调用的时候 C 的入参变成了 [...C,1] 数组长度递增1
  • [...C,1] 的作用,就是保留原数组的内容,在添加一个 1,使得原数组的 length + 1 计数器 效果达成

通过一个简单的拓展运算符,加上一个 1 使得数组的长度不断的变化。达到计数器/加法运算的效果

比如在第 4182 的题目中,需要实现一个 斐波那契数列。斐波那契数列的特性就是 n = (n-1)+(n-2)。如何实现这个加法?用代码来说就是 […(N-1),…(N-2)]。(不理解没关系,后面还有会详细的文章来讲)

数组小结
  • 只要对应的类型 extends [] 的话,就可以使用 [‘length’] 属性获取长度
  • 数组在体操中不仅仅充当了数组的作用,还充当了 计数器 和 加法实现 的作用,用法千变万化
  • 数组的累加依赖于递归方法的实现,在每一次递归的过程中往新的方法里面传入新的长度(不过递归容易造成内存溢出,比如02257-medium-minusone这一题)。需要一个更加高级的技巧处理

as 关键字

在上面讲解数组的时候看到有 as const 的出现,那就顺便讲讲 as

在 TS 使用中,as 就是一个 断言

假设这样的一个场景,有一个变量 todo ,设置为了 Todo 类型,有对应的属性

然后某个函数的副作用,导致了我的 todo 变成了一个 string 类型(实际代码应该规避这种副作用函数)

but 事情就这么发生了,而且不能改,这时候的 todo 应该是 string 类型。todo 还需要调用一个方法,需要传入 stirng 类型的 function todoFn(str:string){} 。这时候直接传入 todo 肯定会报错,类型不符合

解决办法就是 todoFn(todo as string)。看下方的代码截图会好理解一点

在我断定了这个类型就是 xxx 类型的时候就能用 as 关键字(当然不推荐使用),尽可能还是用 TS 的类型推导

最后

中年危机是真实存在的,即便有技术傍身,还是难免对自己的生存能力产生质疑和焦虑,这些年职业发展,一直在寻求消除焦虑的依靠。

  • 技术要深入到什么程度?

  • 做久了技术总要转型管理?

  • 我能做什么,我想做什么?

  • 一技之长,就是深耕你的专业技能,你的专业技术。(重点)

  • 独立做事,当你的一技之长达到一定深度的时候,需要开始思考如何独立做事。(创业)

  • 拥有事业,选择一份使命,带领团队实现它。(创业)

一技之长分五个层次

  • 栈内技术 - 是指你的前端专业领域技术

  • 栈外技术 - 是指栈内技术的上下游,领域外的相关专业知识

  • 工程经验 - 是建设专业技术体系的“解决方案”

  • 带人做事 - 是对团队协作能力的要求

  • 业界发声 - 工作经验总结对外分享,与他人交流

永远不要放弃一技之长,它值得你长期信仰持有

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue 等等。

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值