0
点赞
收藏
分享

微信扫一扫

一文带你了解TypeScript类型断言与类型保护


目录

  • ​​本文概览:​​
  • ​​1. 类型断言​​
  • ​​2. 双重断言​​
  • ​​3. 类型保护​​
  • ​​(1)自定义类型保护​​
  • ​​(2)typeof 类型保护​​
  • ​​(3)instanceof 类型保护​​

本文概览:

一文带你了解TypeScript类型断言与类型保护_字符串

1. 类型断言

TypeScrip的类型系统很强大,但是有时它是不如我们更了解一个值的类型。这时,我们更希望TypeScript不要帮我们进行类型检查,而是让我们自己来判断,则就用到了类型断言。

使用类型断言可以手动指定一个值的类型。类型断言像是一种类型转换,它把某个值强行指定为特定类型:

const getLength = target => {
if (target.length) {
return target.length;
} else {
return target.toString().length;
}
};

这个函数接收一个参数,并返回它的长度。这里传入的参数可以是字符串、数组或是数值等类型的值,如果有 length 属性,说明参数是数组或字符串类型,如果是数值类型是没有 length 属性的,所以需要把数值类型转为字符串然后再获取 length 值。现在我们限定传入的值只能是字符串或数值类型的值:

const getLength = (target: string | number): number => {
if (target.length) { // error 类型"string | number"上不存在属性"length"
return target.length; // error 类型"number"上不存在属性"length"
} else {
return target.toString().length;
}
};

当TypeScript不确定一个联合类型的变量到底是哪个类型时,就只能访问此联合类型的所有类型里共有的属性或方法,所以现在加了对参数target和返回值的类型定义之后就会报错。

这时候,我们就用到了断言,将target的类型断言成string类型。它有两种写法,一种是​​<type>value​​​,一种是​​value as type​​:

// 这种形式是没有任何问题的写法,建议始终使用这种形式
const getStrLength = (target: string | number): number => {
if ((target as string).length) {
return (target as string).length;
} else {
return target.toString().length;
}
};

// 这种形式在JSX代码中不可以使用,而且也是TSLint不建议的写法
const getStrLength = (target: string | number): number => {
if ((<string>target).length) {
return (<string>target).length;
} else {
return target.toString().length;
}
};

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。

注意: 类型断言不要滥用,在万不得已的情况下使用要谨慎,因为强制把某类型断言会造成 TypeScript 丧失代码提示的能力。

2. 双重断言

虽然类型断言是有强制性的,但并不是万能的,因为一些情况下也会失效:

interface Person {
name: string;
age: number;
}
const person = 'ts' as Person; // Error

这个时候会报错,很显然不能把 ​​string​​​ 强制断言为一个接口 ​​Person​​ ,但是并非没有办法,此时可以使用双重断言:

interface Person {
name: string;
age: number;
}
const person = 'ts' as any as Person; // ok

先把类型断言为 ​​any​​ ,再接着断言为想断言的类型就能实现双重断言,当然上面的例子肯定说不通的,双重断言我们也更不建议滥用,但是在一些少见的场景下也有用武之地。

3. 类型保护

类型保护实际上是一种错误提示机制,类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。类型守保护主要思想是尝试检测属性、方法或原型,以确定如何处理值。

先来看一个例子:

const valueList = [123, "abc"];
const getRandomValue = () => {
const number = Math.random() * 10; // 这里取一个[0, 10)范围内的随机值
if (number < 5) {
return valueList[0]; // 如果随机数小于5则返回valueList里的第一个值,也就是123
}else {
return valueList[1]; // 否则返回"abc"
}
};
const item = getRandomValue();
if (item.length) {
// error 类型“number”上不存在属性“length”
console.log(item.length); // error 类型“number”上不存在属性“length”
} else {
console.log(item.toFixed()); // error 类型“string”上不存在属性“toFixed”
}

这个例子中,getRandomValue 函数返回的元素是不固定的,有时返回数值类型,有时返回字符串类型。我们使用这个函数生成一个值 item,然后接下来的逻辑是通过是否有 length 属性来判断是字符串类型,如果没有 length 属性则为数值类型。在 js 中,这段逻辑是没问题的,但是在 TS 中,因为 TS 在编译阶段是无法知道 item 的类型的,所以当在 if 判断逻辑中访问 item 的 length 属性时就会报错,因为如果 item 为 number 类型的话是没有 length 属性的。

这个问题可以通过上面说的类型断言来解决,修改判断逻辑即可:

if ((<string>item).length) {
console.log((<string>item).length);
} else {
console.log((<number>item).toFixed());
}

(1)自定义类型保护

上面的代码不报错是因为通过使用类型断言,告诉 TS 编译器,if 中的 item 是 string 类型,而 else 中的是 number 类型。这样做虽然可以,但是需要在使用 item 的地方都使用类型断言来说明,显然有些繁琐,所以就可以使用类型保护来优化。

可以使用自定义类型保护来解决:

const valueList = [123, "abc"];
const getRandomValue = () => {
const number = Math.random() * 10; // 这里取一个[0, 10)范围内的随机值
if (number < 5) return valueList[0]; // 如果随机数小于5则返回valueList里的第一个值,也就是123
else return valueList[1]; // 否则返回"abc"
};
function isString(value: number | string): value is string {
const number = Math.random() * 10
return number < 5;
}
const item = getRandomValue();
if (isString(item)) {
console.log(item.length); // 此时item是string类型
} else {
console.log(item.toFixed()); // 此时item是number类型
}

首先定义一个函数,函数的参数 value 就是要判断的值,在这个例子中 value 的类型可以为 number 或 string,函数的返回值类型是一个结构为 ​​value is type​​​ 的类型谓语,value 的命名无所谓,但是谓语中的 value 名必须和参数名一致。而函数里的逻辑则用来返回一个布尔值,如果返回为 true,则表示传入的值类型为​​is​​后面的 type。

使用类型保护后,if 的判断逻辑和代码块都无需再对类型做指定工作,不仅如此,既然 item 是 string 类型,则 else 的逻辑中,item 一定是联合类型两个类型中另外一个,也就是 number 类型。

(2)typeof 类型保护

但是这样定义一个函数来用于判断类型是字符串类型,难免有些复杂,因为在 JavaScript 中,只需要在 if 的判断逻辑地方使用 typeof 关键字即可判断一个值的类型。所以在 TS 中,如果是基本类型,而不是复杂的类型判断,可以直接使用 typeof 来做类型保护:

if (typeof item === "string") {
console.log(item.length);
} else {
console.log(item.toFixed());
}

这样直接写效果和自定义类型保护一样。但是在 TS 中,对 typeof 的处理还有些特殊要求:

  • 只能使用​​=​​​和​​!​​两种形式来比较
  • type 只能是​​number​​​、​​string​​​、​​boolean​​​和​​symbol​​四种类型,在 TS 中,只会把这四种类型的 typeof 比较识别为类型保护

如果使用​​typeof {} === ‘object’​​,那它只是一条普通的 js 语句,不具有类型保护具有的效果:

const valueList = [{}, () => {}];
const getRandomValue = () => {
const number = Math.random() * 10;
if (number < 5) {
return valueList[0];
} else {
return valueList[1];
}
};
const res = getRandomValue();
if (typeof res === "object") {
console.log(res.toString());
} else {
console.log(ress()); // error 无法调用类型缺少调用签名的表达式。类型“{}”没有兼容的调用签名
}

(3)instanceof 类型保护

​instanceof​​操作符是 JS 中的原生操作符,它用来判断一个实例是不是某个构造函数创建的,或者是不是使用 ES6 语法的某个类创建的。在 TS 中,使用 instanceof 操作符同样会具有类型保护效果,来看例子:

class CreateByClass1 {
public age = 18;
constructor() {}
}
class CreateByClass2 {
public name = "TypeScript";
constructor() {}
}
function getRandomItem() {
return Math.random() < 0.5 ? new CreateByClass1() : new CreateByClass2(); // 如果随机数小于0.5就返回CreateByClass1的实例,否则返回CreateByClass2的实例
}
const item = getRandomItem();
if (item instanceof CreateByClass1) { // 这里判断item是否是CreateByClass1的实例
console.log(item.age);
} else {
console.log(item.name);
}

这个例子中 if 的判断逻辑中使用 instanceof 操作符判断了 item 。如果是 CreateByClass1 创建的,那么它应该有 age 属性,如果不是,那它就有 name 属性。

总结: 通过使用类型保护可以更好地指定某个值的类型,可以把这个指定理解为一种强制转换,这样编译器就能知道这个值是指定的类型,从而符合预期。typeofinstanceof 是JavaScript 中的两个操作符,用来判断某个值的类型和一个值是否是某个构造函数的实例,它们在 TypeScript 中会被当做类型保护。我们也可以自定义类型保护,通过定义一个返回值类型是"参数名 is type"的语句,来指定传入这个类型保护函数的某个参数是什么类型。如果只是简单地要判断某个值是什么类型,使用 typeof 类型保护就可以。


举报

相关推荐

0 条评论