TS 从 0 到 1 - 类型进阶

2023-05-17 19:44:48 浏览数 (1)

# 断言

# 类型断言

类型断言好比其他语言里面的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。TypeScript会假设你,程序员,已经进行了必须的检查。

  • 尖括号语法
代码语言:javascript复制
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

  • as 语法
代码语言:javascript复制
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

# 非空断言

在上下文中,当类型检查器无法断定类型时,后缀表达式操作符 ! 可以用于断言操作对象的非空性。! 将从操作对象类型里去除 nullundefined

  • 忽略 undefined 和 null
代码语言:javascript复制
function myFunc(maybeString: string | null | undefined) {
  // 'string | null | undefined' 类型不能赋值给 'string' 类型。
  const onlyString: string = maybeString; // Error
  const ingoreUndefinedAndNull: string = maybeString!; // Ok
}

  • 调用函数时忽略 undefined 类型
代码语言:javascript复制
type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
  // NumGenerator 可能为 undefined, 所以不能调用
  const num1 = numGenerator(); // Error
  const num2 = numGenerator!(); // Ok
}

# 确定赋值断言

允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。

代码语言:javascript复制
let x: number;
initialize();

// 变量 x 在赋值前被使用
console.log(2 * x); // Error

function initialize() {
  x = 10;
}

使用确定赋值断言,可以告诉 TypeScript 该属性会被明确地赋值。

代码语言:javascript复制
let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}

# 类型守卫

类型保护时可执行运行时检查的一种表达式,可以确保该类型在一定的范围内。或者说,类型保护可以保证一个字符串是一个字符串,尽管它的值可以是一个数值。

类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。

# in

代码语言:javascript复制
interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnkonwnEmployee = Admin | Employee;

function printEmployeeInformation(emp: UnkonwnEmployee) {
  console.log(`Name: ${emp.name}`);
  if ("privileges" in emp) {
    console.log(`Privileges: ${emp.privileges}`);
  }
  if ("startDate" in emp) {
    console.log(`Start Date: ${emp.startDate}`);
  }
}

# typeof

代码语言:javascript复制
function padLeft (value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding   1).join(" ")   value;
  }
  if (typeof padding === "string") {
    return padding   value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

typeof 类型保护只支持两种形式:typeof v === "typename"typeof v !== "typename""typename" 必须是 "number""string""boolean""symbol"

# instanceof

代码语言:javascript复制
interface Padder {
  getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces   1).join(" ");
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}

let padder: Padder = new SpaceRepeatingPadder(6);

if (padder instanceof SpaceRepeatingPadder) {
  // 类型收窄为 'SpaceRepeatingPadder'
}

# 自定义类型保护

代码语言:javascript复制
function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

# 联合类型和类型别名

# 联合类型

联合类型通常与 nullundefined 一起使用

代码语言:javascript复制
const sayHello = (name: string | undefined) => {
  /* ... */
};

sayHello("Cell");
sayHello(undefined);

类型 A 和 类型 B 联合后的类型是同时接受 A 和 B 的类型。

  • 字面量类型
代码语言:javascript复制
// 字面量类型,用约束取值只能是某几个值中的一个
let num: 1 | 2 = 1;
type EventNames = "click" | "scroll" | "mousemove";

# 可辨识联合

也称为代数数据类型或标签联合类型。包含 3 个要点:可辨识、联合类型、类型守卫。

本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。

  1. 可辨识
代码语言:javascript复制
// 可辨识要求联合类型中的每个元素都含有一个单例属性
enum CarTransmission {
  Automatic = 200,
  Manual = 300
}

interface Motorcycle {
  vType: "motorcycle"; // 可辨识
  make: number;
}

interface Car {
  vType: "car"; // 可辨识
  transmission: CarTransmission;
}

interface Truck {
  vType: "truck"; // 可辨识
  payloadPounds: number;
}

MotorcycleCarTruck 都有一个 vType 属性,它们的值分别是 "motorcycle""car""truck",这就是可辨识属性,其他的属性只跟特性的接口有关。

  1. 联合类型
代码语言:javascript复制
type Vehicle = Motorcycle | Car | Truck;

  1. 类型守卫
代码语言:javascript复制
const EVALUATION_FACTOR = Math.PI;

function evaluatePrice(vehicle: Vehicle) {
  return vehicle.payloadPounds * EVALUATION_FACTOR;
}

const myTruck: Truck = {
  vType: "truck",
  payloadPounds: 10000
};

evaluatePrice(myTruck); // 

以上内容会报错

代码语言:javascript复制
// Property 'payloadPounds' does not exist on type 'Vehicle'.
// Property 'payloadPounds' does not exist on type 'Motorcycle'.

因为在 MotorcycleCarTruck 中都有 vType 属性,但是 Motorcycle 中没有 payloadPounds 属性,所以 evaluatePrice 函数中的 vehicle.payloadPounds 会报错。

代码语言:javascript复制
function evaluatePrice(vehicle: Vehicle) {
  switch (vehicle.vType) {
    case "car":
      return vehicle.transmission * EVALUATION_FACTOR;
    case "motorcycle":
      return vehicle.make * EVALUATION_FACTOR;
    case "truck":
      return vehicle.payloadPounds * EVALUATION_FACTOR;
  }
}

使用 switch 语句来进行类型守卫,从而确保在 evaluatePrice 方法中,可以安全访问 vehicle 所包含的属性。

# 类型别名

类型别名用来给一个类型起个新名字。

代码语言:javascript复制
type Message = string | string[];

let greet = (message: Message) => {
  /* ... */
};

# 交叉类型

在 TypeScript 中交叉类型是将多个类型合并为一个类型,通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,这种类型拥有所有类型的特性。

代码语言:javascript复制
type PartialPointX = { x: number };
type Point = PartialPointX & { y: number };

let point: Point = { x: 1, y: 2 };

# 同名基础类型属性的合并

代码语言:javascript复制
interface X {
  c: string;
  d: string;
}

interface Y {
  c: number;
  e: string;
}

type XY = X & Y;
type YX = Y & X;

XY 都含有一个相同的成员 c,但是它们的类型不一致。对于这种情况,此时 XY 类型或 YX 类型中成员 c 的类型 string & number,这种类型是不存在的,所以此时成员 c 的类型是 never

# 同名非基础类型属性的合并

代码语言:javascript复制
interface D {
  d: boolean;
}
interface E {
  e: string;
}
interface F {
  f: number;
}

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true,
    e: "Cell",
    f: 2022
  }
};

console.log('abc:', abc);

// "use strict";
// let abc = {
//     x: {
//         d: true,
//         e: "Cell",
//         f: 2022
//     }
// };
// console.log('abc:', abc);

在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。

0 人点赞