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)
相关文章
|
2月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
1月前
|
JavaScript
TypeScript——不能将类型“HTMLElement | null”分配给类型“HTMLElement”
TypeScript——不能将类型“HTMLElement | null”分配给类型“HTMLElement”
28 4
|
20天前
|
JavaScript 前端开发 编译器
Angular 与 TypeScript 强强联手太厉害啦!强类型编程带来巨大开发优势,快来一探究竟!
【8月更文挑战第31天】作为一名前端开发者,我致力于探索各种技术框架以提升开发效率与代码质量。近期深入研究了Angular与TypeScript的结合,体验到强类型编程带来的显著优势。Angular是一款强大的前端框架,而TypeScript则是由微软开发的一种强类型语言,为JavaScript增添了静态类型检查等功能。
22 0
|
1月前
|
JavaScript 编译器
typescript 解决变量多类型访问属性报错--工作随记
typescript 解决变量多类型访问属性报错--工作随记
|
30天前
|
JavaScript 前端开发 安全
TypeScript:解锁JavaScript的超级英雄模式!类型系统如何化身守护神,拯救你的代码免于崩溃与混乱,戏剧性变革开发体验!
【8月更文挑战第22天】TypeScript作为JavaScript的超集,引入了强大的类型系统,提升了编程的安全性和效率。本文通过案例展示TypeScript如何增强JavaScript:1) 显式类型声明确保函数参数与返回值的准确性;2) 接口和类加强类型检查,保证对象结构符合预期;3) 泛型编程提高代码复用性和灵活性。这些特性共同推动了前端开发的标准化和规模化。
48 0
|
1月前
|
JavaScript
TypeScript——Record类型
TypeScript——Record类型
32 0
|
1月前
|
JavaScript 前端开发 编译器
Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧
Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧
|
2月前
|
JavaScript 开发者 索引
TypeScript接口与类型别名:深入解析与应用实践
【7月更文挑战第10天】TypeScript的接口和类型别名是定义类型的关键工具。接口描述对象结构,用于类、对象和函数参数的形状约束,支持可选、只读属性及继承。类型别名则为复杂类型提供新名称,便于重用和简化。接口适合面向对象场景,类型别名在类型重用和复杂类型简化时更有优势。选择时要考虑场景和灵活性。
|
2月前
|
JavaScript 前端开发 程序员
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
Typescript 【实用教程】(2024最新版)含类型声明,类型断言,函数,接口,泛型等
56 0
|
2月前
|
JavaScript 安全
TypeScript(十一)泛型工具类型
TypeScript(十一)泛型工具类型
30 0