如何有效地保障类型的安全性, 如下代码(将字符串或字符串数组全部转换为大写形式)会提示参数"strOrArray"隐式具有"any"类型 ts(7006)
function convertToUpperCase(strOrArray) {
if(typeof strOrArray === 'string') {
return strOrArray.toUpperCase()
} else if (Array.isArray(strOrArray)) {
return strOrArray.map(item => item.toUpperCase())
}
}
上述警告信息时因为缺少了参数类型注解,我们可以通过添加string | string[]类型注解来解决掉vscode的错误提示,而if, else if中typeof,Array.isArray将类型返回缩小。这种将类型范围缩小的动作我们称之为类型守卫, 实际上,它还可以用来区分类型集合中的不同成员。
类型集合一般包括联合类型和枚举类型。
如何区分联合类型
常用的类型守卫包括switch, 字面量恒等, typeof,instanceof, in和自定义类型守卫。
switch
在联合类型成员或者成员属性可枚举的场景中使用,即联合类型为字面量值的集合。
const convert = (c: 'a' | 1) => {
swich(c) {
case 1:
return c.toFixed();
case 'a':
return c.toLowerCase();
}
}
字面量恒等
switch适用的场景往往也可以使用字面量恒等比较进行替换。
const convert = (c: 'a' | 1) => {
if (c === 1) {
return c.toFixed()
} else if (c === 'a') {
return c.toLowerCase()
}
}
一般建议,如果可枚举的值和条件分支越多, 使用switch就会让代码逻辑更简介,更清晰
typeof
当联合类型的成员不可枚举,比如说是字符串,数字等原子类型组成的集合,是个时候就需要使用typeof。
instanceof
当联合类型的成员是类是, 使用instanceof来减小类型范围
class Dog {
wang = 'wangwang'
}
class Cat {
miao = 'miaomiao'
}
const getName = (anmial: Dog | Cat) => {
if (animal instanceof Dog) {
return animal.wang
} else if (animal instanceof Cat) {
return animal.miao
}
}
in
当联合类型的成员包含接口类型(对象),并且接口之间的属性不同
interface Dog {
wang: string
}
interface Cat {
miao: string
}
{
const getName = (animal: Dog | Cat) => {
if(typeof animal.wang === 'string') {
return animal.wang // ts2339
} else if (animal.miao) { // ts2339
return animal.miao
}
}
}
{
// 使用in操作符
const getName = (animal: Dog | Cat) => {
if ('wang' in animal) {
return animal.wang; // ok
} else if ('miao' in animal) {
return animal.miao; //ok
}
}
}
自定义类型守卫 is
const isDog = function(animal: Dog | Cat): animal is Dog {
return 'wang' in animal
}
const getName = (animal: Dog | Cat) => {
if (isDog(animal)) {
return animal.wang
}
}
如何区分枚举类型
枚举特性:
- 枚举和其他任何枚举,类型都不可比较, 除了数字枚举可以与数字类型比较除外;
- 数字枚举极其不稳定
最佳实践, 永远不要拿枚举和除了自身之外的任何枚举,类型进行比较
enum A {
one,
two
}
enum B {
one,
two
}
const cpWithNumber = (param: A) => {
if (param === 1) { // bad
return param
}
if (param === B.two as unknown as A) { // bad
return param
}
if (param === A.two) { // good
return param
}
}
字面量成员枚举可等价为字面量成员类型组成的联合类型,所以类型守卫可以让字面量成员枚举发生类型缩小。
失效的类型守卫
ts4.3.5之前版本,某些场景下,比如下面的例子中,类型守卫会失效。
const getName = <T extends Dog | Cat>(animal: T) => {
if('wang' in animal) {
return animal.wang; //ts 2339
}
return animal.miao; // ts 2339
}
in并没有让类型缩小为Dog类型, 可以使用instanceof 来替换in操作符,来缩小类型范围。后面的代码可以使用(animal as Cat).miao
其他
什么时候使用双重断言
A不能直接断言成B, 就需要使用双重断言.
const animal = new Dog()
// 直接断言,会提示ts2352, 两种类型不能充分重叠。 如果是有意的,需要先转换成unknown
animal as Cat
animal as unknown as Cat