学会satisfies操作符,让你的Typescript功力倍增!

今日鸡汤: 人生是海, 总有波涛,需要一颗勇敢的心去乘风破浪!

大家好,我是心灵。

本文是在Typescript中旅行的第 5 篇。将介绍satisfies操作符。

satisfies运算符是一个在typescript项目中非常有用的运算符,在Typescript4.9版本中被引入。但是在日常开发中,强大的satisfies运算符却通常看不到有人在使用, 这可能是Typescript官方手册中并没有介绍这一特性,而仅在4.9版本记录中介绍了satisfies。不为人所熟知。

本文将会讨论如下几个问题:

  1. satisfies是什么且如何使用?
  2. 有什么作用?
  3. as的区别是什么?
  4. 使用场景

satisfies是什么?

先不说satisfies晦涩的官方定义,我们先来看官网的一个朴实无华的例子(略作修改,更好理解):

ts 复制代码
type Colors = 'red' | 'green' | 'blue'
type RGB = [red: number, green: number, blue: number]
const palette: Record<Colors, string | RGB> = {
	red: [255, 0, 0],
	green: '#00ff00',
	blue: [0, 0, 255]
}
// Both of these methods are still accessible!

const greenNormalized = palette.green.toUpperCase() // ~~~~~~ error!

ts在编辑器上抛出了一个错误❌。

这是因为TypeScript不确定palette.greenstring还是RGB,因为我们将palettevalue定义为了string | RGB的联合类型。所以palette.green可以是它们中的任何一个。而RGB类型没有toUpperCase方法,一切都是为了类型安全。

为了让这行表达式正常执行,我们可以使用类型缩小的概念,使用if语句手动验证属性的类型:

ts 复制代码
let greenNormalized
if (typeof palette.green === 'string') {
	greenNormalized = palette.green.toUpperCase() 
}

使用if进行类型缩小似乎有些冗余,因为作为开发者我们知道palette对象中green属性就是string类型的。所以有没有一种办法能让Typescript确保值与类型匹配,但又希望保留该类型的更具体的类型用来进行类型推导 。听起来有些拗口,其实就是当我们想让palette.green这个表达式根据它的值*'#00ff00'*去自动匹配string类型,而不是string | RGB

可能你会想到as const,但是它并不是很好的解决方案(后面会说到),现在我们使用satisfies操作符对palette变量进行管理后得到了我们想要的结果。

非常完美!我们没有做其他复杂的操作,仅只是使用了satisfies修饰符,green被推断出来了string类型,palette.green.toUpperCase()表达式也就不会报错了。而且类型安全也不会丢失。

当尝试给blue定义一个number类型的变量也会进行正确的类型安全校验。

当尝试给palette定义一个不在预期范围中的key

多余的属性检查

作用

  1. 保证类型的正确性,可以检查给定类型是否满足特定条件。

  2. 使用satisfies操作符在对变量进行管理的时候会准确的推断出值的类型,即使值是联合类型

  3. satisfies推断出来的是窄类型,而不是更宽的类型,且值优于类型。

与as 的不同

  • as表示断言,有一种强制的意思,而且最关键的一点是as可能会向编辑器说谎!,这是as的特性决定的,而satisfies不会。

红框中的代码会在运行时报错!

  • as const处理变量

as const也可以让palette.green也可以修改该报错, 但是as const 会将变量变为radonly类型。此时palette.green被推断为字面量类型, 我们无法对palette.green进行赋值操作。在有些时候我们不想要这样的效果。

  • 变量标注、as const与satisfies

    当我们想要标注一个变量的类型,但是又想要其不可变,可以用as const

    ts 复制代码
    interface Result {
    	retCode: 200 | 404 | 500
    }
    
    const responseTableData: Result = {
    	retCode: 200
    } as const
    
    responseTableData.retCode = 404 // ???

    当变量标注与as const一起配合使用时,比变量标注覆盖了as const ,所以无法达到我们想要的效果。

    这个时候satisfies能完成我们想做的事情。

    ts 复制代码
    interface Result {
            retCode: 200 | 404 | 500
    }
    
    const responseTableData = {
            retCode: 200
    } as const satisfies Result
    
    responseTableData.retCode = 404

使用场景和其他例子

  1. satisfies译为满足,所以在一些对象需要满足某种特定形状或是特定属性时来使用。
ts 复制代码
interface RouteRow {
	path: string
	redirect?: string
	name?: string
	component: any
}

const routes = [
	{
		path: '/',
		component: 'main'
	},
	{
		path: '/login',
		component: 'login'
	}
] as const satisfies RouteRow[]

as const强制routes的不可变性,satisfies RouteRow[]强调routes要满足RouteRow[]的形状。

  1. 配合never进行详尽性检查
  1. 根据业务需要我们定义了一个Student类型,Student的属性和值的类型被确定下来

    ts 复制代码
    type Keys = 'name' | 'age' | 'company' | 'email'
    const staff = {
    	name: '小明',
    	age: 18,
    	company: 'alibaba',
    	email: '123456'
    } satisfies Partial<Record<Keys, string | number>>
    
    staff.name.toLowerCase()
    staff.age.toFixed()

    上面的name属性和age属性都准确推断出了类型。

总结

satisfies运算符提供了一种灵活的方式来进行类型检查,同时保留了表达式的原始类型。如果使用了satisfies运算符ts会根据提供的类型和变量的值对变量进行类型检查和推断。推断的类型会更窄,能够准确的推断出值的类型。在下次需要使用类型缩小或是as之前,可以先考虑到是否能够替换成satisfies来解决。

往期文章

本篇是Typescript系列的第五篇,下面是往期文章

相关推荐
风骏时光牛马1 分钟前
稳定性治理及疑难问题深度剖析与实战复盘
前端
JeffongTan8 小时前
在LWC中镶嵌VF Page获取用户IP
前端·javascript·salesforce
不cong明的亚子10 小时前
web live多屏互动平台
javascript·经验分享·音视频
陆枫Larry11 小时前
Astro 是什么
前端
是立不是利12 小时前
写给设计师的 CSS 博客美学指南
前端·css
dsyyyyy110113 小时前
JS复习,难点,易错点总结
开发语言·javascript·ecmascript
默_笙13 小时前
🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南
前端·javascript
计算机魔术师13 小时前
Rohan Paul 谈用时间跨度衡量智能体能力,并引用 OpenAI 自动化研究实习生里程碑
前端
kyriewen13 小时前
我手写了个极简版 React Router——才搞懂 v6 为什么砍了这么多 API
前端·javascript·程序员