TS 类型体操还能这么玩,太秀了

TS类型体操技巧总结 最近刷类型体操有点上瘾,本文算是最近刷类型体操的一些思考的集合。刷的时候感觉自己长脑子了,但是过段时间又好像没脑子了,还是得通过博客总结沉淀一下 判断是否是 never 类型 typescript type IsNever<T> = [T] extends [never] ? true : false; type IsNever<T> = (() => T) ex... 阅读详情

最近在看 TypeScript 相关的内容,做了一下类型体操,真的太秀啦

递归、infer 满天飞,今天就来领略一下 TS 能做什么骚操作吧!

先放上本文的几个小标题,很骚

  • 巧用数组上数学课
  • 模版字符串为所欲为
  • 中序遍历 TS 也能行
  • infer + 递归随意秒杀

下面开始军训体操

一、巧用数组上数学课

这一题是 TS 类型挑战中的 Greater Than

这道题需要我们实现 GreaterThan<T, U> 判断 T > Utrue 还是 false

有几个特殊测试用例

GreaterThan<2, 1> //should be true
GreaterThan<1, 1> //should be false
GreaterThan<10, 100> //should be false
GreaterThan<111, 11> //should be true 

看到这题,在 JS 中非常的简单,直接就能有答案,但是 TS 是没有计算能力的,也不支持大小判断

那么我们还能怎么做呢?巧用数组,通过数组的 length 来进行比较

有两种可行的方法

  • 第一种就是递归,但是通过实践我们会知道,当参数过大时,很容易爆栈
  • 第二种方法就是构造两个长度为 TU 的数组,通过数组判断哪个数组更长

这里我们先看第一种方法

递归法

可以采用递归来实现,前面我们也有说过了,数组的很容易爆掉,但是测试用例还算温柔,这题能过

  • 思路是拿一个新数组,和 T,U 进行对比,哪个先追上新数组的长度,哪个就小
  • 简单一点来说就是,两个不一样长的木棍放在一起,我们从一端开始不断往前走,先摸到的那个木棍就是短一点的

看到具体实现上

通过引入新的变量 R extends any[] = [] ,来进行辅助的计算,接着依次判断 TUR['length] 是否相等,这时候,如果 TR['length] 相等了而 U 还没有相等,那就说明了 T < U ,如果都不相等,那就继续加大数组 R 的长度

怎么加大数组 R 呢?

递归的时候,往数组中多加一个值即可,GreaterThan<T, U, [...R, 1]> 这里的 1 就是塞进去把 length 整大的

这样就完成了这道大小判断

// 答案
type GreaterThan<T extends number, U extends number, R extends any[] = []> = T extends R['length']? false: U extends R['length']? true: GreaterThan<T, U, [...R, 1]> 

构造数组

我们还有一种很巧妙的方法

先看几段小代码

[1, 1, 1, 1] extends [1, 1] ? true : false // false
[1, 1] extends [1, 1, 1] ? true : false // false 

上面的例子中,两个数组长度不等,很显然都会返回 false

那么我们抽象一下,这同样会返回 false,因为很显然多了 ...any

// 伪代码
A extends [...A, ...any] ? true : false 

那么我们就可以有这样的思路,例如比较 2 和 3

我们就可以比较数组 A[1, 1] 和数组B [1, 1, 1]

我们就可以这么来看,数组 B 可以表示为 [...A, 1] 所以 B 大于 A,A 没有办法这样表示 B 所以更小

显然我们这个思路没啥问题!

如何实现呢?关键在于怎么构造长度为 T 和 U 的数组

写一个生成长度为 N 的数组的方法,需要接受长度 T,还需要使用辅助变量 A 来保存当前的数组并作为返回值

采用递归的方式往数组中添加新的元素,这样数组的 ['length'] 就会不断的变长,当等于 T 时,就结束循环,返回数组 A

type newArr<T extends number, A extends any[] = []> = A['length'] extends T? A: newArr<T, [...A, '']> 

验证一下,没啥毛病

type A = newArr<4> // type A = ["", "", "", ""] 

接下来就好办了,我们比较这两个数组就好了,为了美观抽出一个 type 来,这个就是我们前面讲到的逻辑

type GreaterArr<T extends any[], U extends any[]> = U extends [...T, ...any] ? false : true 

最后调用它进行比较,KO

type GreaterThan<T extends number, U extends number> = GreaterArr<newArr<T>, newArr<U>> 

二、模版字符串为所欲为

这一节,来看看模版字符串在 TS 里有多骚

这一题是 3326 · BEM style string,我们需要实现 BEM 函数完成其规则拼接,不理他,直接看用例

type ClassNames1 = BEM<'btn', ['price']> // 'btn__price'
type ClassNames2 = BEM<'btn', ['price'], ['warning', 'success']> // 'btn__price--warning' | 'btn__price--success'
type ClassNames3 = BEM<'btn', [], ['small', 'medium', 'large']> // 'btn--small' | 'btn--medium' | 'btn--large' 

不过就是根据参数的位置,用不同的符号进行连接,例如BEN<'aaa', ['b'], ['c']>

b 是第二个参数,那么就用 __ 来连接,c 是第三个参数就用 -- 来连接

这题怎么做呢,我们只需要根据不同的参数利用模版字符串定义不同的模版即可

但是你会发现我们传参是数组形式的,我们要的是一个个的,那就需要通过下标来将数组或者对象转成联合类型

// 数组转联合
T[number]
// 对象转联合
Object[keyof T] 

特殊的,当字符串中通过这种方式申明时,会自动生成新的联合类型,例如这题下面的写法,

type BEM<B extends string, E extends string[], M extends string[]> = `${B}__${E[number]}--${M[number]}` 

会得到 type A = "btn__price--warning" | "btn__price--success" 这样的结果

但是这并没有考虑到空数组的情况,因此需要做提前的判断,

type IsNever<T> = [T] extends [never] ? true : false
type IsUnion<U> = IsNever<U> extends true ? "" : U
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${IsUnion<`__${E[number]}`>}${IsUnion<`--${M[number]}`>}` 

模版字符串想拼啥就拼啥,酷!

三、中序遍历 TS 也能行

JS 实现中序遍历,你闭着眼睛就能写,那 TS 呢

这是我们的测试用例,我们需要实现 InorderTraversal 方法,来实现中序遍历

const tree1 = {val: 1,left: null,right: {val: 2,left: {val: 3,left: null,right: null,},right: null,},
} as const

type A = InorderTraversal<typeof tree1> // [1, 3, 2] 

这题看上去很难,TS 怎么还能遍历树呢,其实是可以的,非常简单,和 JS 的思路是一致的,我们先看看 JS 是如何实现中序遍历的呢?

const inorderTraversal = (root) => {if(!root) return []const res = []while(root) {inorderTraversal(root.left)res.push(val)inorderTraversal(tree.right)}return res
} 

JS 是在 root 为 null 时结束。对于 TS 来说,实现递归,需要 extends TreeNode 而不是 null 来结束

不能使用 null 来判断,是因为 TS 不能判断类型 T 是否符合 TreeNode 类型

在结束前,我们需要递归的调用这个方法,左中右的顺序

// 答案
interface TreeNode {val: numberleft: TreeNode | nullright: TreeNode | null
}
type InorderTraversal<T extends TreeNode | null> = [T] extends [TreeNode] ? ([...InorderTraversal<T['left']>,T['val'],...InorderTraversal<T['right']>]): [] 

四、infer + 递归随意秒杀

infer 可谓是 TS 中的大杀器,大多数题目都会涉及到它的使用,他可以很方便的帮我们推断出一个变量的类型,我们看看这道题

实现一个像 Lodash.without 函数一样的泛型 Without<T, U>,它接收数组类型的 T 和数字或数组类型的 U 为参数,会返回一个去除 U 中元素的数组 T。

type Res = Without<[1, 2], 1> // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]> // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]> // expected to be [] 

不用看题目啦,直接看用例,无非就是把第二个参数中的值,从数组中去掉

这题我们非常容易想,通过 infer 和 递归来实现,用 infer 取出数组的第一项

  • 如果能够被 U 包含,那就丢弃,也就是把剩余的递归,不保留这一项
  • 如果不包含,那就用 [R, ...] 把它给留下,剩下的继续递归 因此很有可能写下这样的代码
type Without<T, U> = T extends [infer R, ...infer F]? R extends U? Without<F, U>: [R, ...Without<F, U>]: T 

但是发现只过了一个用例,问题在于 U 有可能是数组,也有可能是字符串,而单纯采用 extends 来判断只能处理字符串的情况

因此我们需要解决如何判断字符串和数组两种情况

可以采用数组转 Union 的方法来解决

type ToUnion<T> = T extends any[] ? T[number] : T
type B = ToUnion<['1','b']> // type B = "1" | "b" 

这样无论是数字还是数组,都会转成联合类型,而联合类型很方便判断 extends 包含关系:

// 答案
type ToUnion<T> = T extends any[] ? T[number] : T
type Without<T, U> = T extends [infer R, ...infer F]? R extends ToUnion<U>? Without<F, U>: [R, ...Without<F, U>]: T 

总结

这篇文章通过几道例题,带大家领略了 TS 的风采,也看到了 TS 的弊端:计算能力,在使用 TS 过程中,我们要

  • 巧用辅助变量
  • 遇到计算时大胆使用 ['length']
  • 诡异字符串操作多用模版字符串
  • infer + 递归大杀器

好了本文的内容就这么多了,更多关于 TS 的内容,以后再说,随缘更新!

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

TypeScript泛型和类型体操 泛型和类型体操是 TypeScript 中强大的类型系统的关键组成部分。通过使用泛型,我们可以创建可重用、灵活和类型安全的代码。内置泛型函数提供了一些常用的类型转换工具,如PartialRequired和Pick,可以帮助我们更方便地处理类型操作。通过结合泛型、extends关键字、内置泛型函数和其他高级类型概念,我们能够在 TypeScript 中编写更复杂、类型安全的代码,并利用 TypeScript 的强大类型系统来提高代码的可读性、可维护性和可扩展性。 阅读详情

相关推荐

高缺失率时空数据插补实战:从随机森林到扩散模型的性能对比

数据缺失是数据分析与机器学习建模中普遍面临的挑战,尤其在时空数据领域,传感器故障、传输中断等问题常导致数据不完整。数据插补技术旨在基于已知数据的模式和关联性,合理估算缺失值,为后续分析提供可靠基础。其核心原理在于利用数据的时空相关性(如时间周期性、空间邻近性)及与外部驱动因素(如交通、气象)的关联构建预测模型。这项技术的价值在于能将不完整的数据集转化为可用于高质量分析和预测的可用资产,广泛应用于环境监测、交通流量预测、金融风控等领域。本文聚焦于一个极具挑战性的场景——PM2.5监测数据缺失率高达82%,通过

weixin_30363285的博客 505

TS】1470- 总结 TS 类型体操的9种类型运算、4种类型套路

今天给大家分享的主题是一起来做类型体操。主要分为 4 个部分进行介绍:类型体操的背景,通过背景了解为什么要在项目中加入类型体操;了解类型体操的主要类型、运算逻辑、和类型套路;类型体操实践,解析 TypeScript 内置高级类型,手写 ParseQueryString 复杂类型;小结,综上分享,沉淀结论。一、背景—在背景章节介绍的是什么是类型,什么是类型安全,怎么实现类型安全,什么是类型体操?以了...

pingan8787 2116

JavaScript 类型转换的巧妙应用:探索 TS 类型体操的更多可能性!

TS 类型体操是一种利用 TypeScript 的类型系统进行变量转换和操作的技巧。通过巧妙地使用 TypeScript 的类型推断和类型转换功能,我们可以在编译时对变量的类型进行灵活的操作,从而实现一些有趣而富有创造性的效果。TS 类型体操的核心思想是使用类型转换操作符as,将一个类型转换为另一个类型。这使得我们可以在 TypeScript 中像操作普通数据一样操作类型,从而实现一些特殊的功能。下面,让我们通过一些具体的示例来进一步理解 TS 类型体操的应用。

debug_bug142的博客 238

TS 体操 &(交叉运算) 和 接口的继承的区别

深入了解 TS中 & 和 extend 的异同。根据这个特性出发重新思考定义类型时到底是用type 还是用 interface

Jioho的博客 1981

成为优TS体操高手 之 TS 类型体操前置知识储备

TS 类型体操前置知识储备如果你正在学习 TS,可是像我一样仅仅停留在定义类型,定义interface/type的层面的话, 这份体操类型练习题一定不要错过 !type-challenges写这篇文章的时候我只做完了体操类型的中等级别的题目(还有 2-3 道其实没完全解出来)更多面试题库看前端面试题题库简单搭建一下做题环境我喜欢做一题拷贝一题,所以我 clone 了......

web全栈开发的博客 2178

TS 类型体操

本文总结了TypeScript类型体操的核心概念与技巧,将类型系统比作函数式编程环境。主要内容包括: 类型体操本质:在类型系统中进行函数式编程,将JS值转换为类型,函数转为条件类型,递归函数转为递归类型等。 类型结构与运算原语:介绍了Tuple/Object/Union/String等数据结构及其操作符,如extends/infer/keyof等,强调类型体操是"操作类型数据结构"。 思维与算法模型:提出五大核心操作能力,包括递归处理、条件分支、映射转换、类型推断和联合类型操作,这些能力

青蛙king的博客 1271

TS为什么被叫做类型体操

类型系统不止ts有,比如说java、c++都有,为什么只有ts被称为类型体操本文将讲述为什么ts被称为类型体操。静态类型编程语言都有一套自己的类型系统,从简单到复杂可以分为3类。

shmily的博客 1331

TS类型体操:实现axios的链式调用类型提示

fill:#333;color:#333;color:#333;fill:none;调用方法A调用方法B调用方法C对象实例返回对象自身返回对象自身最终结果。

2202_75708745的博客 1136

TS 类型体操 之 数组的用法与进阶(加法,减法,乘法)

用亿点点小学知识,在TS中实现 “加减乘” 法,加深对TS infer 的特性和数组特性的用法

Jioho的博客 3154

TypeScript与纯JavaScript互操作性会带来哪些挑战?

当使用JavaScript库时,如果没有为这些库提供TypeScript类型定义文件(d.ts文件),开发者可能需要手动添加类型定义,或者依赖于any类型来避免类型错误。当使用不支持TypeScript的库时,开发者可能需要自己编写类型定义或使用第三方提供的类型定义,这可能会增加额外的工作量。而TypeScript强调静态类型检查,这可能导致在处理JavaScript的动态特性时出现类型错误或需要额外的类型断言。例如,当使用回调函数时,可能需要显式地指定回调函数的参数类型,以确保类型安全。

liuhao9999的博客 770

这才是真正让你入门 TypeScript 类型体操的文章,确实如此!

大厂技术高级前端Node进阶点击上方程序员成长指北,关注公众号回复1,加入高级Node交流群通过本文你能学到什么?keyof 操作符的使用TypeScript 中的映射类型使用,以及映射类型结合as 关键字的使用extends 条件判断类型的基本使用infer 关键字的使用通过这些基础知识真实场景改造你现在的代码(用其中一篇文章 函数参数的例子来写)如果你觉得你 TypeScript 已...

程序员成长指北 713

typescript 高级类型体操

类型体操练习

jsphpyyds的博客 1054

Ts类型体操详讲 之 extends infer (上)

什么是条件提取?本节 我们来讲述 Ts类型体操 使用 extends ? 配合 infer 做条件提取,我会通过代码示例 去讲解数组、字符串、函数、构造器、索引等类型如何去使用条件提取 说明: 首先我们定义了一个 Arr 的 类型别名,表示一个包含六个数字(1 到 6)的元组。然后我们定义了一个工具函数,它首先检查传入的类型 T 是否是数组,如果 T 不是数组,getArrFirst 的结果就是 never。如果 T 是数组,它会进一步检查 T 是否有一个明确的第一个元素和后续的元素。

shmily的博客 1146

仓颉语言与ArkTS互操作

在 OpenHarmony 系统上,ArkTS 具备完整广泛的生态,为复用 ArkTS 生态,仓颉支持与 ArkTS 高效跨语言互通。仓颉-ArkTS 互操作基于仓颉 CFFI 能力,通过调用 ArkTS 运行时接口,为用户提供库级别的 ArkTS 互操作能力。

程小二的博客 3039

JavaScript 的互操作性

TypeScript 作为 JavaScript 的超集,其核心价值在于与现有 JavaScript 生态的无缝互操作性。本文探讨了 TypeScript 编译为 JavaScript 的机制,包括基本流程、配置选项和错误处理,确保输出代码兼容各种环境。同时介绍了处理动态代码的策略,如使用 unknown 类型类型守卫来平衡静态检查与运行时灵活性。渐进式迁移策略是重点,从文件重命名到逐步添加类型检查,再到启用严格模式,提供了可控的迁移路径。实际应用中,通过声明文件和类型断言可实现 TS 与 JS 的混合使

zimin的专栏 916

TS 类型体操 之 extends,Equal,Alike 使用场景和实现对比

深入对比了TS中常用的几种判断方法,extends,Equal,Alike。深入了解 Equal 背后实现的原理。更好的理解这几种判断常用的应用场景

Jioho的博客 3482

2024年全国青少年信息素养大赛初赛真题(算法创意实践挑战赛C++小学组:带解析)

2024全国青少年信息素养大赛C++算法创意实践挑战赛初赛真题及解析

王老师青少年编程 2752

论文初稿模版

我的论文模版,研究生论文模版,仅供参高。

上一篇: React Fiber 入门浅学
下一篇: uni-app 怎么实现路由拦截
东方睡衣
博客等级 码龄4年 226粉丝 · 359原创
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值