TypeScript中的satisfies关键字,比as更准确,比`:Type`类型定义更简洁

太长不看版:satisfies关键字,可以检验类型而不改变类型。相比:Type类型定义,satisfies Type会在检查类型的同时保留原本的隐式类型推导。而as任何时候都不能用于检查类型

例子::Type类型定义,或使用as转换,导致信息丢失

颜色的表示,有两种方式,一种是RGB,如rgb(255, 255, 255)。另一种是十六进制,如#FFFFFF。前者在TypeScript中,可以用对象表示,后者用字符串表示。因此,Color类型定义如下。

ts 复制代码
type RGBColor = {
  red: number;
  green: number;
  blue: number;
};

type HexColor = string;
type Color = RGBColor | HexColor;

接着,定义画板的类型Canvas。它有前景色foregroundColor与背景色backgroundColor两属性。

ts 复制代码
type Canvas = {
      // 前景色
      foregroundColor: Color;
      // 背景色
      backgroundColor: Color;
}

编码的进程继续推进,接着我们定义了变量canvas1。但留下了错误的代码。

ts 复制代码
const canvas1 = {
  backgroundColor: { red: 255, green: 0, bleu: 0 },
  //                                      ^
  //                                      这里blue拼错了
  foregroundColor: "#000000",
}

在编码时,把blue误写成了bleu。因为我们没有使用任何TypeScript的类型检查,无法在编码阶段发现错误。

加上类型定义:Canvas,能检出错误

ts 复制代码
const oneCanvas: Canvas = {
  backgroundColor: { red: 255, green: 0, bleu: 0 },
  foregroundColor: "#000000",
};

编译器提示:

Type '{ red: number; green: number; bleu: number; }' is not assignable to type 'Color'.

Object literal may only specify known properties, but 'bleu' does not exist in type 'RGBColor'. Did you mean to write 'blue'?ts(2322)

使用as Canvas同样可以检出错误

ts 复制代码
const oneCanvas = {
  backgroundColor: { red: 255, green: 0, bleu: 0 },
  foregroundColor: "#000000",
} as Canvas;

as是对等号右侧的对象进行了类型转换,TS编译器抱怨转换无法完成。

Conversion of type '{ backgroundColor: { red: number; green: number; bleu: number; }; foregroundColor: string; }' to type 'Canvas' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

Types of property 'backgroundColor' are incompatible.

Type '{ red: number; green: number; bleu: number; }' is not comparable to type 'Color'.

Property 'blue' is missing in type '{ red: number; green: number; bleu: number; }' but required in type 'RGBColor'.ts(2352)

通过:Canvas类型定义,或使用as转换类型,发现并纠正blue写成bleu的笔误后。带来了一个新的问题:无法像as转换前那样直接访问canvas1.backgroundColor.red

ts 复制代码
const canvas1 = {
  backgroundColor: { red: 255, green: 0, blue: 0 },
  foregroundColor: "#000000",
} as Canvas;

const canvas2 = {
  backgroundColor: { red: 255, green: 0, blue: 0 },
  foregroundColor: "#000000",
};

console.log(canvas2.backgroundColor.red)
console.log(canvas1.backgroundColor.red);
//                                   ^
//                                  提示字段不存在

报错信息如下

Property 'red' does not exist on type 'Color'.

Property 'red' does not exist on type 'string'.ts(2339)

报错信息直白明确。第二行提示red字段是不会存在于string(HexColor)类型中的。

canvas1.backgroundColor的类型是联合类型Color。而red字段只有当ColorRGBColor的时候才存在。

因此,需要收紧类型,才能访问到red字段。

ts 复制代码
if (typeof canvas1.backgroundColor === 'object') {
  console.log(canvas1.backgroundColor.red);// 不报错
}

可canvas1是通过字符串字面量定义的,canvas1.backgroundColor.red字段一定是存在的。typeof做类型收紧是为了迁就类型声明或as转换带来的类型丢失。

satisfies关键字: 校验但不改变类型

对上面的代码中canvas1部分进行修改

ts 复制代码
const canvas1 = {
  backgroundColor: { red: 255, green: 0, blue: 0 },
  foregroundColor: "#000000",
} satisfies Canvas;
type TypeCanvas1 = typeof canvas1;
console.log(canvas1.backgroundColor.red); // 不报错

第五行对canvas1.backgroundColor.red的访问通过了TypeScript的校验。

TypeCanvas1来自于类型系统对等号右侧的对象字面量的推导。同时TypeCanvas1能与Canvas类型"兼容"。TypeCanvas1Canvas的子类,所以能通过satisfies的检验。

使用satisfies的2种经典场景

函数传参时原地校验

一些Javascript的API,如JSON.stringify,它们的参数类型会被定为any。若不使用satisfies,则需要定义一个变量并定义类型,才能对参数进行校验。而satisfies可直接就地检验。

不用satisfies

ts 复制代码
const canvas1: Canvas = {
  backgroundColor: { red: 255, green: 0, blue: 0, XXYYAABB: 1 },
  ////////////////////////////////////////           ^ 会在这里报错
  foregroundColor: "#000000",
};

JSON.stringify(canvas1);

要检出对象字面量的错误必须声明一个变量并给出:Canvas类型定义。

使用satisfies

ts 复制代码
JSON.stringify({
  backgroundColor: { red: 255, green: 0, blue: 0, XXYYAABB: 1 },
  ////////////////////////////////////////           ^ 会在这里报错
  foregroundColor: "#000000",
})

这个场景可以扩展到,调用fetch, 设置searchParams等一切参数被定义为any但需要校验的场合。使用satisfies校验类型能让代码更加健壮。

as const联用,保留常量类型的同时检验常量类型

Typescript 的类型系统,会隐式推断类型。as const则会使这种类型推断更加精准,对于numberstring的值,会被推断为一个常量而非numberstring类型本身。对于数组则会推断为一个固定长度的元组。同时加上readonly修饰符。

举一个as const的例子。

ts 复制代码
const a1 = [{ foo: 2 }]

const a2 = [{ foo: 2 }] as const;

a1a2的类型都来自于隐式的类型系统推断。其中a1的类型是{ foo: string }[]a2的类型,因为加了as const,类型会被推断为[{ foo: 1 }]

在等号左侧使用:Type类型定义,会导致as const推断出的类型丢失。而satisfies只校验不转换类型能检查类型,同时保留as const的类型推断结果。

ts 复制代码
type Route = {
  path: string;
};

const routes = {
  HOME: { path: '/' },
  USER: { path: '/user' },
  REGISTER: { path: '/register' }
} as const satisfies Record<string, Route>;

function navigate(path: '/' | '/user') {
}

navigate(routes.HOME.path); // 不报错,参数的类型是`/`
navigate(routes.REGISTER.path); // 报错, 参数的类型是`/register`

在这段代码中,satisfies Record<string, Route>校验了routes对象各个字段的值。同时as const推断出的类型信息被保留。第14行校验能通过,而15行则被TypeScript检出错误。

深入研究::Type类型定义、assatisfies的底层逻辑

标题中说,satisfies检验类型,比as更准确。甚至在上文的例子中,都尽量避免使用as。现在到了揭晓原因的时刻。

下面的代码,wrong1wrong2竟然都能通过类型系统的检查。不符合预期。

ts 复制代码
const wrong1 = {} as Route;
const wrong2 = { path: 1, OTHERS: 2 } as Route;

原因是:as被设计出来,就不是用来做类型检查的。它的功能是做类型转换。检查出的错误是类型系统认为不能转换产生的错误。

任何时候都不要用as去做类型检查

类型检查只有satisfies:Type定义才能做到

TypeA as TypeB,当B可以作为A的子类型时,类型系统的检查就能通过。

TypeA satisfies TypeBconst foo:TypeB = a正好相反。只有类型B能作为类型A的父类时,才能通过类型系统的检验。

一个形象的比喻:

as让类型站在地板上向上跳跃,satisfies检查地板是否稳固

:Type类型定义,与satisfies的唯一区别:前者会显式地声明类型,后者只检查类型同时保留原有隐式推断的类型。和as const配合时也能尽可能多的保留类型。

相关推荐
追光少年33221 小时前
Learning Vue 读书笔记 Chapter 4
前端·javascript·vue.js
软件2051 小时前
【Vite + Vue + Ts 项目三个 tsconfig 文件】
前端·javascript·vue.js
老大白菜1 小时前
在 Ubuntu 中使用 FastAPI 创建一个简单的 Web 应用程序
前端·ubuntu·fastapi
渔阳节度使1 小时前
React
前端·react.js·前端框架
LCG元3 小时前
Vue.js组件开发-如何实现异步组件
前端·javascript·vue.js
Lorcian3 小时前
web前端12--表单和表格
前端·css·笔记·html5·visual studio code
问道飞鱼3 小时前
【前端知识】常用CSS样式举例
前端·css
wl85113 小时前
vue入门到实战 三
前端·javascript·vue.js
ljz20164 小时前
本地搭建deepseek-r1
前端·javascript·vue.js
爱是小小的癌4 小时前
Java-数据结构-优先级队列(堆)
java·前端·数据结构