TypeScript学习【2】TypeScript 的复杂基础类型

本文介绍了TypeScript中的数组类型定义方法,包括使用[]符号和Array泛型,并探讨了元组类型及其应用场景。此外,还详细讲解了any、unknown等特殊类型的作用与使用注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下接《TypeScript 的简单基础类型》

数组

数组类型(Array)

在 TypeScript 中,我们也可以像 JavaScript 一样定义数组类型,并且指定数组元素的类型。

首先,我们可以直接使用 [] 的形式定义数组类型,如下所示:

let StringArr: string[] = ['7', 'Jae', 'Pr'];
let NumberArr: number[] = [7, 8, 9];

同样,我们也可以使用 Array 泛型定义数组类型,如下所示:

let StringArr: Array<string> = ['7', 'Jae', 'Pr'];
let NumberArr: Array<number> = [7, 8, 9];

以上两种定义数组类型的方式虽然本质上没有任何区别,但是更推荐使用 [] 这种形式来定义。一方面可以避免与 JSX (一种 JavaScript 的语法扩展,运用于 React 架构中)的语法冲突,另一方面可以减少不少代码量。

如果我们明确指定了数组元素的类型,以下所有操作都将因为不符合类型约定而提示错误:

let NumberArr: number[] = ['x', 'y', 'z']; // 提示 ts(2322)

NumberArr[3] = 'a'; // 提示 ts(2322)

NumberArr.push('b'); // 提示 ts(2345)

let StringArr: string[] = [1, 2, 3]; // 提示 ts(2322)

StringArr[3] = 1; // 提示 ts(2322)

StringArr.push(2); // 提示 ts(2345)

元组类型(Tuple)

元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。

网上对于 TS 中元祖类型的解释太过于复杂,我们仅需知道数组是合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。直接看代码示例就好:

let nameAndage: [string, number];
nameAndage[0] = 'Jae';
nameAndage[1] = 23;
// error TS2454: Variable 'nameAndage' is used before being assigned
// 会报错,因为在赋值前使用了变量。非严格模式下不会报错


// 当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项
let nameAndage: [string, number] = ['Jae'];
// index.ts(1,5): error TS2322: Type '[string]' is not assignable to type '[string, number]'
// 会报错,不能将类型“[string]”分配给类型“[string, number]”。源具有 1 个元素,但目标需要 2 个


let nameAndage: [string, number] = ['Jack', 18];
nameAndage[0] = 'Jae';
nameAndage[1] = 23;
// 一切正常,没有报错

特殊类型

any

any 指的是一个任意类型,它是官方提供的一个选择性绕过静态类型检测的作弊方式。

我们可以对被注解为 any 类型的变量进行任何操作,包括获取事实上并不存在的属性、方法,并且 TypeScript 还无法检测其属性是否存在、类型是否正确。

比如我们可以把任何类型的值赋值给 any 类型的变量,也可以把 any 类型的值赋值给任意类型(除 never 以外)的变量,如下代码所示:

let one: any = {};
one = 1; // 不会报错
one = 'hi'; // 不会报错

let num: number = one; // 不会报错
let str: string = one; // 不会报错

如果我们不想花费过高的成本为复杂的数据添加类型注解,或者已经引入了缺少类型注解的第三方组件库,这时就可以把这些值全部注解为 any 类型,并告诉 TypeScript 选择性地忽略静态类型检测。

尤其是在将一个基于 JavaScript 的应用改造成 TypeScript 的过程中,我们不得不借助 any 来选择性添加和忽略对某些 JavaScript 模块的静态类型检测,直至逐步替换掉所有的 JavaScript。

习惯性地使用 any 是一个坏习惯。如果一个 TypeScript 应用中充满了 any,此时静态类型检测基本起不到任何作用,也就是说与直接使用 JavaScript 没有任何区别。

unknown

unknown 是 TypeScript 3.0 中添加的一个类型,它主要用来描述类型并不确定的变量。

比如在多个 if else 条件分支场景下,它可以用来接收不同条件下类型各异的返回值的临时变量,如下所示:

let res: unknown;
if (x) {
  res = x();
} else if (y) {
  res = y();
}

与 any 不同的是,unknown 在类型上更安全。比如我们可以将任意类型的值赋值给 unknown,但 unknown 类型的值只能赋值给 unknown 或 any,如下代码所示:

let res: unknown;
let num: number = res; // ts(2322) Type 'unknown' is not assignable to type 'number'
let anything: any = res; // 一切正常

使用 unknown 后,TypeScript 会对它做类型检测。但是,如果不缩小类型,我们对 unknown 执行的任何操作都会出现如下所示错误:

let res: unknown;
result.toFixed(); // ts(2571) Object is of type 'unknown'

而所有的类型缩小手段对 unknown 都有效,如下所示:

let res: unknown;
if (typeof res=== 'number') {
  res.toFixed(); // 不会提示错误
}

void、undefined、null

这三种类型没有太大用处,我们只需要了解:

  • void仅适用于表示没有返回值的函数。即如果该函数没有返回值,那它的类型就是 void
  • undefined 的最大价值主要体现在接口类型上,它表示一个可缺省、未定义的属性
  • null 的价值主要体现在接口制定上,它表明对象或属性可能是空值

依照官方的说法,它们实际上没有太大的作用,尤其是在本篇中使用的strict模式下,它们更是没有太大用处。如果你还想拓展,那就继续看以下内容吧~

首先我们来说一下 void 类型,在 strict 模式下,声明一个 void 类型的变量几乎没有任何实际用处,因为我们不能把 void 类型的变量值再赋值给除了 any 和 unkown 之外的任何类型变量。

然后是 undefined 类型 和 null 类型,它们是 TypeScript 值与类型关键字同名的唯二例外。但这也没有什么大用处😂,因为单纯声明 undefined 或者 null 类型的变量也是无比鸡肋的,示例如下所示:

let undeclared: undefined = undefined;
let nullable: null = null;

undefined 的最大价值主要体现在接口类型(后续篇章讲),它表示一个可缺省、未定义的属性。

而 null 的价值我认为主要体现在接口制定上,它表明对象或属性可能是空值。尤其是在前后端交互的接口,任何涉及查询的属性、对象都可能是 null 空对象,如下代码所示:

const userInfo: {
  name: null | string
} = { name: null }; 

除此之外,undefined 和 null 类型还具备警示意义,它们可以提醒我们针对可能操作这两种(类型)值的情况做容错处理。

我们需要类型守卫(后续篇章讲)在操作之前判断值的类型是否支持当前的操作。类型守卫既能通过类型缩小影响 TypeScript 的类型检测,也能保障 JavaScript 运行时的安全性,如下代码所示:

const userInfo: {
  id?: number,
  name?: null | string
} = { id: 1, name: 'Jae' };
if (userInfo.id !== undefined) {
  userInfo.id.toFixed(); // id 的类型缩小成number
}

不建议随意使用非空断言(下一篇讲)来排除值可能为 null 或 undefined 的情况,因为这样很不安全:

userInfo.id!.toFixed(); // ok,但不建议
userInfo.name!.toLowerCase(); // ok,但不建议

比非空断言更安全、类型守卫更方便的做法是使用单问号(Optional Chain)、双问号(空值合并),我们可以使用它们来保障代码的安全性,如下代码所示:

userInfo.id?.toFixed();
const name = userInfo.name ?? 'Jae'

never

never 表示永远不会发生值的类型,如下所示:

// 圆括号后 :+ 类型注解,表示函数返回值的类型
function throwError(msg: string): never {
  throw Error(msg);
}

以上函数因为永远不会有返回值,所以它的返回值类型就是 never

同样,如果函数代码中是一个死循环,那么这个函数的返回值类型也是 never,如下代码所示。

function InfiniteLoop(): never {
  while (true) {}
}

never 是所有类型的子类型,它可以给所有类型赋值;但是反过来,除了 never 自身以外,其他类型(包括 any 在内的类型)都不能为 never 类型赋值

let res: never;
res = 1; // ts(2322) Type 'number' is not assignable to type 'never'
res = '1'; // ts(2322) Type 'string' is not assignable to type 'never'
let num: number = res; // 一切正常
let str: string = res; // 一切正常

在恒为 false 的类型守卫条件判断下,变量的类型将缩小为 never(never 是所有其他类型的子类型,所以是类型缩小为 never,而不是变成 never)。因此,条件判断中的相关操作始终会报无法更正的错误(我们可以把这理解为一种基于静态类型检测的 Dead Code 检测机制),如下代码所示:

const str: string = 'string';
if (typeof str === 'number') {
  str.toLowerCase(); // Property 'toLowerCase' does not exist on type 'never'.ts(2339)
}

基于 never 的特性,我们还可以使用 never 实现一些有意思的功能。比如我们可以把 never 作为接口类型下的属性类型,用来禁止写接口下特定的属性,示例代码如下:

const props: {
  id: number,
  name?: never
} = {
  id: 1
}
props.name = null // ts(2322)
props.name = 'str' // ts(2322)
props.name = 1 // ts(2322)

此时,无论我们给 props.name 赋什么类型的值,它都会提示类型错误,实际效果等同于 name 只读 。

object

object 类型表示非原始类型的类型,即非 number、string、boolean、bigint、symbol、null、undefined 的类型。然而,它也是个没有什么用武之地的类型,如下所示的一个应用场景是用来表示Object.create的类型:

declare function create(o: object | null): any;
create({}); // ok
create(() => null); // ok
create(2); // ts(2345)
create('str'); // ts(2345)

类型断言(Type Assertion)

TypeScript 类型检测无法做到绝对智能,毕竟程序不能像人一样思考。有时会碰到我们比 TypeScript 更清楚实际类型的情况,比如下面的例子:

const arrNumber: number[] = [1, 2, 3, 4];
const moreThan2: number = arrNumber.find(num => num > 2); // ts(2322) 不能将类型“number | undefined”分配给类型“number”

其中,moreThan2一定是一个数字(确切地讲是 3),因为 arrNumber中明显有大于 2 的成员,但静态类型对运行时的逻辑无能为力。

在 TypeScript 看来,moreThan2的类型既可能是数字,也可能是 undefined,所以上面的示例中提示了一个 ts(2322) 错误,此时我们不能把类型 undefined 分配给类型 number。

不过,我们可以使用一种笃定的方式——类型断言(类似仅作用在类型层面的强制类型转换)告诉 TypeScript 按照我们的方式做类型检查。

比如,我们可以使用 as 语法做类型断言,如下代码所示:

const arrNumber: number[] = [1, 2, 3, 4];
const moreThan2: number = arrNumber.find(num => num > 2) as number;
console.log(moreThan2); // 3

又或者是使用尖括号 + 类型的格式做类型断言,如下代码所示:

const arrNumber: number[] = [1, 2, 3, 4];
const moreThan2: number = <number>arrNumber.find(num => num > 2);
console.log(moreThan2); // 3

以上两种方式虽然没有任何区别,但是尖括号格式会与 JSX 产生语法冲突,因此更推荐使用 as 语法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大杯美式不加糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值