学会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系列的第五篇,下面是往期文章

相关推荐
志尊宝几秒前
Vue3 零基础每日笔记(027):组件上的 v-model——让自定义组件也能双向绑定
前端·javascript·vue.js·笔记·html5
云浪8 分钟前
从 0 手写一个 MCP Server:让 Copilot 调用 Tool 完成四则运算
javascript·node.js·mcp
萧行之2 小时前
可视化技术复盘:从图形语法看 D3、Vega-Lite、ECharts、Observable Plot
前端·学习·数据可视化
尾善爱看海10 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
驳是10 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea11 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持11 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者11 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn11 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
计算机魔术师13 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端