如何用好TypeScript 的 any unknown 类型

在开发过程中,你肯定遇到过不知道怎么写类型了,那应该怎么办?

又或者说你在把一个 JavaScript 项目迁移到 TypeScript,一时半会也搞不清到底这个变量在这里是什么类型?

any 类型

为了解决这种情况,TypeScript 特意为你提供了 any 类型,它的使用方式就和你此前已经学习的类型标注是完全一样的,只需要替换掉原本的类型即可:

js 复制代码
let name: any;

function fn(params: any): any { ... }

const list: any[] = [1, "hello", true];

let obj: any = { prop1: "hello", prop2: 123 };

这样替换之后,对原本的类型有什么影响吗?

any 主打的就是一个"任意"。

在类型层面,我们也可以这么理解,any 类型 = string + number + boolean + 任意对象类型 + 拥有任意参数类型与任意返回值类型的函数类型 + ...,它就是无所不包的。

因此,在不知道对一个变量提供何种类型时,就可以使用 any 类型来作为临时性的过渡方案

但为什么只是临时过渡?

因为,既然 any 类型能表示所有类型,那它还能带给我们精确的类型提示吗?

当然不能,实际上使用了 any 类型,就意味着告诉类型检查系统,这个变量我给它开白名单了,你放过它吧,它想干啥就干啥:

js 复制代码
let foo: any = 'Hi';

foo..handler();

很明显这段代码实际执行是会报错的,但是由于你自己声明了放弃类型检查,它并不会被检查出来。

但是,这很明显是相当危险的行为,也和我们选择 TypeScript 的原因相悖,因为使用了 any 类型之后,要想代码能够正常运行,你其实又相当于恢复到之前使用 JavaScript 时期。

所以我们说它是临时性的过渡方案。

一旦你完成了对整个程序逻辑的梳理,最后还是应该把正确的具体类型填补上去

总结一下你会发现,any 类型 = 万能类型 + 放弃类型检查 ,其中万能类型 是我们想要的,能不能只要这个部分,而不要放弃类型检查这个危险的行为呢?

当然可以。

考虑到 any 类型的危险性,TypeScript 中还提供了一个功能类似的家伙:unknown 类型,用于表示万能类型的同时,保留类型检查。

unknown 类型

我们先看万能类型的部分:

js 复制代码
function myFunc(param: unknown) {
  // ...
}

myFunc({});
myFunc([]);
myFunc(true);

很不错,看起来都没报错,和 any 类型一样好用。但如果我们尝试在这个函数内使用参数呢?

js 复制代码
function myFunc(param: unknown) {
  param.forEach((element) => {}); // X "param"的类型为"未知"。
}

我们发现,在尝试使用一个 unknown 类型的变量时,类型检查系统阻止了我们,它要求我们先为这个变量提供一个具体的类型后才能使用。

而我们这里调用了 forEach 方法,很明显,我们希望它是一个数组类型。但此时在代码中,param 的类型已经被固定为 unknown,那我们应该如何修改一个变量的类型?

类型断言

这个时候我们就可以引入第二个新概念------类型断言了。

可以的把他简单理解为,它能够修改一个变量的类型,无论是TS自己推导的,还是你手动标注的。

这个概念的重要之处在于,此前我们学习到的类型标注就像是一次成型,一旦你为这个变量提供了类型,或者是赋值之后,这个变量的类型就已经固定了,我们无法再对它进行修改。

而现在有了类型断言,我们现在可以指着这个变量告诉 TS,这个类型看起来是一个字符串,其实它是一个数字

回到上面的例子,如果要将 unknown 类型的变量断言到数组类型,可以这么写:

typescript 复制代码
function myFunc(param: unknown) {
  (param as unknown[]).forEach((element) => {});
}

将参数类型断言到了一个成员类型为 unknown 的数组类型。虽然我们心里希望 element 是数字类型,但是 TS 可猜不到。此时,你可以考虑将 param 的类型一步到位的完善,也可以在后续使用时一步步完善:

js 复制代码
function myFunc(param: unknown) {
  (param as number[]).forEach((element) => {
    element = element + 1;
  });
}

function myFunc(param: unknown) {
  (param as unknown[]).forEach((element) => {
    element = (element as number) + 1;
  });
}

这两种方式使用起来并没有明显的差异,但第二种一步步断言的方式更能体现类型断言的意义:一个变量最开始是未知的类型,但随着后续的一步步使用,我们通过类型断言慢慢地完善这个类型的轮廓,最后完成对初始类型的定义。

总结一下,any 类型和 unknown 类型都能提供万能类型的作用,但不同之处在于,使用 any 类型后就丧失了类型检查的保护,可以对变量进行任意操作。而使用 unknown 类型时,虽然我们每进行一次操作都需要进行类型断言,断言到当前我们预期的类型,但这却能实现类型信息反向补全的功能,为最终我们的具体类型埋下伏笔。虽然 any 类型的使用过程中也可以通过类型断言保障,但毕竟缺少了类型告警,我们很容易就忽略掉了

上面我们了解的是初始提供 any / unknown 类型,然后通过类型断言将其断言到预期类型的操作。实际上,还有一个更常见的场景是将一个拥有具体类型的变量断言到 any / unknown 类型

js 复制代码
const str: string = "linbudu";

(str as any).handler().result.prop; 

为什么我们需要这么做?

因为很多时候,你面临的项目中并不会是完全没有类型定义的,这些变量可能最开始也是被维护者精心设计了类型的,但随着项目的不断迭代和维护者的更替,它们才日渐年久失修,导致你在使用这些变量时需要面对大量的类型报错。所以这个时候我们就可以请出类型断言,先将其断言到一个万能类型,然后就重复我们上面学习的,随着一步步调用不断完善类型,然后最后回头补全的过程。

另外一个常见的场景是,某些时候 TypeScript 的类型分析会显得不那么符合直觉,比如这个例子:

js 复制代码
interface IUser {
  name: string;
  job?: IJob;
}

interface IJob {
  title: string;
}

const user: IUser = {
  name: 'foo',
  job: {
    title: 'bar',
  },
};

const { name, job = {} } = user;

const { title } = job; // 类型"{}"上不存在属性"title"。

由于我们在第一次解构赋值时,为 job 提供了一个空对象作为默认值,TypeScript 会认为此时 job 的类型就是一个空对象,所以我们在第二次解构赋值时,就无法从 job 上获得 title 属性了。要解决这个问题,我们可以在第一次解构赋值时将这个空对象断言到预期的类型:

js 复制代码
const { name, job = {} as IJob } = user;

const { title } = job;

总结

不同于此前我们学习的类型标注中,原始类型和对象类型标注能够对应到 JavaScript 中的数据类型,any 与 unknown 类型是全新的概念,赋予了我们描述"任意类型"的能力,而 unknown 则是为了解决 any 类型过于无拘无束的特点而诞生的。

同时,由于某些变量可能在交到我们手里时就已经获得了自己的初始类型,要使用 any 和 unknown 作为新的变量类型,我们就需要类型断言的帮助,它的作用当然就是将变量类型断言到一个新的类型。通过配合 unknown 类型和类型断言,我们就能够在描述任意类型的同时,确保代码中实际逻辑的安全。

相关推荐
下雪天的夏风2 小时前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript
天下无贼!16 小时前
2024年最新版TypeScript学习笔记——泛型、接口、枚举、自定义类型等知识点
前端·javascript·vue.js·笔记·学习·typescript·html
Jorah3 天前
1. TypeScript基本语法
javascript·ubuntu·typescript
小白小白从不日白4 天前
TS axios封装
前端·typescript
aimmon4 天前
Superset二次开发之源码DependencyList.tsx 分析
前端·typescript·二次开发·bi·superset
下雪天的夏风5 天前
Vant 按需引入导致 Typescript,eslint 报错问题
前端·typescript·eslint
theMuseCatcher5 天前
Vue3+TypeScript+Vite+Less 开发 H5 项目(amfe-flexible + postcss-pxtorem)
typescript·less·postcss
Qiyandays5 天前
vue + Lodop 制作可视化设计页面 实现打印设计功能(四)
前端·vue.js·typescript
人工智能的苟富贵5 天前
微信小程序中的模块化、组件化开发:完整指南
微信小程序·小程序·typescript
Code blocks8 天前
小试牛刀-区块链Solana多签账户
算法·typescript·区块链·github