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配合时也能尽可能多的保留类型。

相关推荐
多多米100543 分钟前
初学Vue(2)
前端·javascript·vue.js
柏箱1 小时前
PHP基本语法总结
开发语言·前端·html·php
新缸中之脑1 小时前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz8561 小时前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习1 小时前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
blaizeer2 小时前
深入理解 CSS 浮动(Float):详尽指南
前端·css
编程老船长2 小时前
网页设计基础 第一讲:软件分类介绍、工具选择与课程概览
前端
编程老船长2 小时前
网页设计基础 第二讲:安装与配置 VSCode 开发工具,创建第一个 HTML 页面
前端
速盾cdn2 小时前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学12 小时前
CSS浮动
前端·css·css3