TypeScript类型保护

1,811 阅读2分钟

联合类型适合于那些值可以为不同类型的情况, 但当我们想确切地了解是否为 某个类型时怎么办? JavaScript里常用来区分2个可能值的方法是检查成员是否存在。

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
    return
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

// 每一个成员访问都会报错
if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}

为了让这段代码工作,我们要使用类型断言:

let pet = getSmallPet();

if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}

假若我们一旦检查过类型,就能在之后的每个分支里清楚地知道 pet的类型的话就好了。

TypeScript里的 类型保护机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个 类型谓词:

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

在这个例子里, pet is Fish就是类型谓词。 谓词为 parameterName is Type这种形式, parameterName必须是来自于当前函数签名里的一个参数名。

每当使用一些变量调用 isFish时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

// 'swim''fly' 调用都没有问题了
let pets: Bird = {
  fly: function() {
    console.log('fly')
  },
  layEggs: function() {

  }
};
if (isFish(pets)) {
    pets.swim();
}
else {
    pets.fly();
}

注意TypeScript不仅知道在 if分支里 petFish类型; 它还清楚在 else分支里,一定 不是 Fish类型,一定是 Bird类型。

完整代码如下:

interface Bird {
  fly();
  layEggs();
}

interface Fish {
  swim();
  layEggs();
}

function getSmallPet(): Fish | Bird {
  // ...
  return {
    fly: function() {
      console.log('fly')
    },
    layEggs: function() {
  
    }
  }
}

let pet = getSmallPet();
pet.layEggs(); // okay
//pet.swim();    // errors
if ((<Fish>pet).swim) {
  (<Fish>pet).swim()
}

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

if (isFish(pet)) {
  pet.swim()
} else {
  pet.fly()
}

有个奇怪的地方,如果我重新定义一个Bird变量,代码没有问题

let pet1: Bird = {
  fly: function() {
    console.log('fly')
  },
  layEggs: function() {

  }
};
if (isFish(pet1)) {
  pet1.swim()
} else {
  pet1.fly()   //ok
}

但是如果定义一个Fish的变量,则会报错

let pet2: Fish = {
  swim: function() {
    console.log('swim')
  },
  layEggs: function() {

  }
};
if (isFish(pet2)) {
  pet2.swim()
} else {
  pet2.fly()  //error
}

但是通过函数的方式,两个定义都可以正常显示

function getSmallPet(): Fish | Bird {
  // ...
  return {
    fly: function() {
      console.log('fly')
    },
    layEggs: function() {
  
    }
  }
}

let pet = getSmallPet();

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

if (isFish(pet)) {
  pet.swim()
} else {
  pet.fly()  //ok
}

function getSmallPet(): Fish | Bird {
  // ...
  return {
    swim: function() {
      console.log('swim')
    },
    layEggs: function() {
  
    }
  }
}

let pet = getSmallPet();

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

if (isFish(pet)) {
  pet.swim()
} else {
  pet.fly()  //ok
}