TypeScript中的高级类型

简介: TypeScript中的高级类型

TypeScript中的高级类型

类型别名 type

现在我们有这样一个代码,如果要再声明一个同样类型的对象,我们需要再重复声明一次类型。

我们应该尽可能复用我们的代码。

let man: {
  readonly name: string;
  age: number;
  retire: (date: Date) => void;
} = {
  age: 22,
  name: "kevin",
  retire: (date: Date) => {
    console.log(date);
  },
};

所以我们使用了type关键字

type Man = {
  readonly name: string;
  age: number;
  retire: (date: Date) => void;
};

let man: Man = {
  age: 22,
  name: "kevin",
  retire: (date: Date) => {
    console.log(date);
  },
};

当然,你也可以使用接口interface关键字,这个关键字我们会在后面讲。

联合类型|

我们可以使用联合类型,来声明一个变量的类型为多个类型的子集。

使用|来声明联合类型:

但是,这时候,我们发现使用 ts 的代码补全时,只有 number 和 string 类型的共同的方法。

所以,我们要想办法将类型范围“缩小”:

可以看到:现在的代码补全就是对应类型的方法了

我们再查看编译出的 js 代码:

"use strict";
const func = (param) => {
  if (typeof param === "number") return param;
  else return parseInt(param) * 1.2;
};

可以看到,我们所以联合类型只是 ts 编译器对变量进行的类型检查

交叉类型&

我们可以使用&来表示同时满足多个类型,也许你会想到这样声明一行代码:

let number_string: number & string;

但这是不合理的,因为没有值能够既是 number 类型又是 string 类型

我们通常用它来声明一个类型:

type Draggable = {
  drag: () => void;
};

type Resizable = {
  resize: () => void;
};

type UIWidget = Draggable & Resizable;

let textBox: UIWidget = {
  drag: () => {},
  resize: () => {},
};

字面量类型

假设我们有一个变量为quantity

let quantity: number;

这个变量的类型为 number,也就是说我们可以赋值任何数值给这个变量。

但也许我们就只想给它赋值固定的数值?

ts 允许我们使用字面量来声明类型,如 string,number,boolean 类型的值

let quantity: 50 = 100; //报错:不能将类型“100”分配给类型“50”。ts(2322)

或许你觉得这没有什么用,但当我们使用联合类型时,或许就有用了:

let quantity: 50 | 100 = 100;

当然,我们应该这样写:

type Quantity = 50 | 100;
let quantity: Quantity = 100;

对于字符串和布尔类型:

type Metric = "cm" | "m";
type Bools = true | false;

null 类型

只有 null 为 null 类型,只有 undefined 为 undefined 类型

假如我们有一个函数:

const greet = (name: string) => {
  console.log(name.toUpperCase());
};
greet(null); //报错:类型“null”的参数不能赋给类型“string”的参数。ts(2345)

如果你想去除这个报错,你可以(不推荐)在 tscofig.json 中找到"strictNullChecks": true,打开并该为 false

或许你真的想传入 null 类型,你可以声明联合类型:

当然,这里你不能光声明类型,因为在你使用name.toUpperCase()时它会报错:name 可能为 null。

const greet = (name: string | null | undefined) => {
  if (name) console.log(name.toUpperCase());
  else console.log("hello");
};
greet(null);
greet(undefined);

可选链/可选属性访问符?

现在假定我们有一个函数,用来输出客户的生日,或许你不知道客户的名字。我们将 name 的类型定义为与 null 和 undefined 联合的类型

type Customer = {
  birthday: Date;
};

function getCustomer(id: number): Customer | null | undefined {
  return id === 0 ? null : { birthday: new Date() };
}

let customer = getCustomer(0);
if (customer !== null && customer !== undefined) console.log(customer.birthday);

在 ts 中,我们可以通过可选链/可选属性访问符(在.前使用),当这个方法/属性被定义时我们调用,如果为null或者undefinedts 会返回undefined,并且有短路运算的特性。

type Customer = {
  birthday: Date;
};

function getCustomer(id: number): Customer | null | undefined {
  return id === 0 ? null : { birthday: new Date() };
}

let customer = getCustomer(0);
console.log(customer?.birthday); //undefined

现在我们让Customer类型中的birthday属性为可选,然后链式调用 getFullYear 方法。注意加上可选属性访问符。因为这时的customer?.birthday可能为 null 或者 undefined:

type Customer = {
  birthday?: Date;
};

function getCustomer(id: number): Customer | null | undefined {
  return id === 0 ? null : { birthday: new Date() };
}

let customer = getCustomer(0);
console.log(customer?.birthday?.getFullYear()); //注意getFullYear()前的可选属性访问符

同时,我们也可以在数组和调用方法中使用:

function tryGetFirstElement<T>(arr?: T[]) {
  return arr?.[0];
  // equivalent to
  //   return (arr === null || arr === undefined) ?
  //       undefined :
  //       arr[0];
}
async function makeRequest(url: string, log?: (msg: string) => void) {
  log?.(`Request started at ${new Date().toISOString()}`);
  // roughly equivalent to
  //   if (log != null) {
  //       log(`Request started at ${new Date().toISOString()}`);
  //   }

  const result = (await fetch(url)).json();

  log?.(`Request finished at at ${new Date().toISOString()}`);

  return result;
}

无效合并/Nullish Coalescing??

假如我们有这样的代码:

let speed: number | null = null;
let ride = {
  speed: speed ? speed !== null : 30,
};

在 ts 中,我们可以使用无效合并来简化代码:

let speed: number | null = null;
let ride = {
  speed: speed ?? 30,
};

当处理 null 或者 undefined 时,它可以作为一种「倒退」到默认值的方式

类型断言

有些时候,我们比 ts 编译器更清楚变量/常量的类型

let phone = document.getElementById("phone"); //let phone: HTMLElement | null

假设我们真的有一个元素的 id 为 phone,那么变量 phone 的类型就应该为 HTMLElement 而不是 HTMLElement | null

并且前面的经验也告诉我们这不利于代码补全。

我们可以使用as关键字

let phone = document.getElementById("phone") as HTMLElement;

并且这时候的代码推断的提示也是 HTMLElement 类型的方法。

当然,我们也可以使用尖括号来进行断言<>

let phone = <HTMLElement>document.getElementById("phone");

unknown 比 any 更安全

之前我们已经讲过,any 虽然会让我们避免报错,但大量的使用 any 会让我们失去使用 ts 的意义。同样的 ts 也不鼓励我们使用 any,而是推荐我们使用 unknown,意思是”不知道是什么类型“。

不过我们直接使用 unknown 时会报错:

function reder(document: unknown) {
  document.toUpperCase(); //报错:对象的类型为 "unknown"。ts(2571)
}

这是 unknown 类型的主要价值主张:TypeScript 不允许我们对类型为 unknown 的值执行任意操作。相反,我们必须首先执行某种类型检查以缩小我们正在使用的值的类型范围。

像前面提到的那样,我们进行类型范围的缩小:

function reder(document: unknown) {
  if (typeof document === "string") document.toUpperCase();
}

但 typeof 只能用于基本类型,而像自定义类型,我们需要用到instanceof关键字

class Behavior {
  move() {}
}

function reder(document: unknown) {
  if (document instanceof Behavior) document.move();
}

never 类型

never 代表永远不会发生的类型。它是 TypeScript 中的底层类型。它自然被分配的一些例子:

  • 一个从来不会有返回值的函数(如:如果函数内含有 while(true) {});
  • 一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') }foo 的返回类型是 never);

例如:

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

processEvents();
console.log("never do"); //这行代码在vscode中会呈灰色,如果不使用never类型,这会看上去能够执行

我们还可以在 tsconfig.json 中找到"allowUnreachableCode": true, 打开并改为false这表示我们不允许不能到达的代码

现在我们得到了一个提示,这对我们是很有用的:

这也是为什么我们要使用 never。

同样的:

let bar: (err: string) => never = (err: string) => {
  throw new Error(err + ":Throw my hands in the air like I just dont care");
};
bar("...");
console.log("never do"); //检测到无法访问的代码。ts(7027)
相关文章
|
23天前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
38 0
|
23天前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧
|
7天前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。
|
18天前
|
JavaScript 前端开发
TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第11天】TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
21天前
|
JavaScript 前端开发 安全
TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第9天】TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
23天前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与最佳实践
【10月更文挑战第8天】深入理解TypeScript:类型系统与最佳实践
|
23天前
|
移动开发 JavaScript 前端开发
TypeScript:数组类型&函数使用&内置对象
本文介绍了 TypeScript 中的数组类型、对象数组、二维数组、函数、函数重载、内置对象等概念,并通过代码示例详细展示了它们的使用方法。还提供了一个使用 HTML5 Canvas 实现的下雨效果的小案例。
|
6天前
|
JavaScript 前端开发 安全
TypeScript进阶:类型系统与高级类型的应用
【10月更文挑战第25天】TypeScript作为JavaScript的超集,其类型系统是其核心特性之一。本文通过代码示例介绍了TypeScript的基本数据类型、联合类型、交叉类型、泛型和条件类型等高级类型的应用。这些特性不仅提高了代码的可读性和可维护性,还帮助开发者构建更健壮的应用程序。
13 0
|
30天前
|
JavaScript 前端开发 安全
使用 TypeScript 加强 React 组件的类型安全
【10月更文挑战第1天】使用 TypeScript 加强 React 组件的类型安全
37 3
|
23天前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧