TypeScript中联合类型的类型保护

在TypseScript中引入Redux给action加类型验证的时候我们考虑对于同一个请求的三个action的type是不是可以直接将类型设置为string。
例如:

export interface ActionType {
  type: string;
}

export interface RequestManufacturerSuccess extends ActionType {
  payloadUsAs: Manufacturer[];
}

export interface RequestManufacturerFailed extends ActionType {
  payloadUsAs: any;
}

export type RequestManufacturerAction =
    ActionType | RequestManufacturerSuccess | RequestManufacturerFailed;

但是我们在reducer中发现如果给action的type直接限定类型为string时,reducer无法通过不同type找到对应的不同的actionType,代码如下:

const initState: ManufacturerState = { manufacturers: [], error: null, loading: false };

function reducer(state = initState, action: RequestManufacturerAction): ManufacturerState {
  switch (action.type) {
    case REQUEST_MANUFACTURERS:
      return { ...state, loading: true };
    case REQUEST_MANUFACTURERS_SUCCESS:
      // 会提示 property action.payload does not exist on type ActionType
      return { ...state, manufacturers: action.payload, loading: false };
    case REQUEST_MANUFACTURERS_FAILED:
      // 会提示 property action.payload does not exist on type ActionType
      return { ...state, error: action.payload, loading: false };
    default:
      return state;
  }
}

这里就涉及到了TypeScript union type的概念和特性了:

联合类型概念:

union type基本形式:使用 | 分隔不同类型

export type pad = number | string

表示如果一个变量或参数或函数是pad类型则它可以为number类型或者string类型;

export type RequestManufacturerAction =
    ActionType | RequestManufacturerSuccess | RequestManufacturerFailed;

同理,上面的代码表示如果一个变量或参数或函数是RequestManufacturerAction类型则它可以为ActionType类型或者RequestManufacturerSuccess类型或RequestManufacturerFailed类型。

但是在reducer中使用这个联合类型时,我们想通过传入的action的不同type让它自动识别对应到不同的actionType使得我们能够获取该类型的属性时,它无法做到这一点。

实际上,如果我们使用了union type,我们在使用该联合类型的参数时,只能使用联合类型中所有类型的公共部分。例如:

//types.ts
export interface Bird {
  type: string;
  fly: () => string;
  layEggs: () => string;
}

export interface Fish {
  type: string;
  swim: () => string;
  layEggs: () => string;
}

export type SmallPet = Bird | Fish;
//index.ts
import {Bird, Fish, SmallPet} from "./types";

function distinguishPet(smallPet: SmallPet) {
  switch (smallPet.type) {
    case 'bird':
        return smallPet.fly; //errors
    case 'fish':
        return smallPet.swim; //errors
    default:
      return smallPet.layEggs; //okay
  }
}

联合类型在这里看来似乎有点复杂,如果我们有A | B这样的类型,我们只能确定A和B共有的部分,在这个例子中,Bird 有一个fly成员,但是我们无法确定Bird | Fish类型中有fly成员。如果变量确实为Fish类型,那么在运行时smallPet.fly会失败。

Type Guard 和 Differentiating Types

我们可以通过Type Guard 和 Differentiating Types的方式去确保union type的各种情况,使其可以使用不同类型的属性:

类型断言

为了使上面的情况可以工作,我们可以使用类型断言的方式:

function distinguishPet(smallPet: SmallPet) {
  switch (smallPet.type) {
    case 'bird':
        return (smallPet as Bird).fly;
    case 'fish':
      if ("swim" in smallPet) {
        return (smallPet as Fish).swim;
      }
  }
}

自定义 Type Guards

Type Predicates

定义一个 type guard,我们只需要定义一个返回类型为type predicate的函数
例如:

function distinguishPet(smallPet: SmallPet) {
  switch (smallPet.type) {
    case 'bird':
      if (isBird(smallPet)) {
        return smallPet.fly;
      }
    case 'fish':
      if (isFish(smallPet)) {
        return smallPet.swim;
      }
    default:
      return smallPet.layEggs;
  }
}

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function isBird(pet: Fish | Bird): pet is Bird {
  return (pet as Bird).fly !== undefined;
}

不论何时isFish被调用,TypeScript会进一步缩小变量类型的范围到一个比较精确的类型。

'in' 操作符

in 操作符在这里作为缩小类型的表达式

function distinguishPet(smallPet: SmallPet) {
  switch (smallPet.type) {
    case 'bird':
      if ("fly" in smallPet) {
        return smallPet.fly;
      }
    case 'fish':
      if ("swim" in smallPet) {
        return smallPet.swim;
      }
    default:
      return smallPet.layEggs;
  }
}

typeof type guards

const BIRD = 'BIRD';
const FISH = 'FISH';

export interface Bird {
  type: typeof BIRD;
  fly: () => string;
  layEggs: () => string;
}

export interface Fish {
  type: typeof FISH;
  swim: () => string;
  layEggs: () => string;
}

export type SmallPet = Bird | Fish;
import {SmallPet} from "./types";

function distinguishPet(smallPet: SmallPet) {
  switch (smallPet.type) {
    case 'BIRD':
        return smallPet.fly;
    case 'FISH':
        return smallPet.swim;
    default:
      const {layEggs} = smallPet;
      return layEggs;
  }
}

在这里TypeScript会把typeof识别为它自己的一个type guards.

instanceof type guards

export interface Tree{
  getTreeNumber(): string;
}
import {Tree} from "./types";

export class TreesIncrement implements Tree{
  constructor(private treeNumber: number){}
  getTreeNumber(){
    return Array(this.treeNumber+1).join(" ");
  }
}

export class TreesNumber implements Tree{
  constructor(private treeNumber: string){}
  getTreeNumber(){
    return this.treeNumber;
  }
}

function getRandomTreeNumber() {
  return Math.random() < 0.5 ? new TreesIncrement(4) : new TreesNumber("3");
}

let tree: Tree = getRandomTreeNumber();

if(tree instanceof TreesIncrement){
  console.log(tree);
}

if(tree instanceof TreesNumber){
  console.log(tree);
}

instanceof 右边需要是一个contructor函数。instanceof type guards 是一个使用其contructor函数实现精确type的方法。

参考链接:https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types

你可能感兴趣的:(TypeScript中联合类型的类型保护)