# TypeScript 进阶
- 类型断言
- 类型保护
- 类型推断
# 类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
# 语法
// 两种写法,在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 值 as 类型。
值 as 类型
<类型>值
TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。
const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'
这里的代码发出了错误警告,因为 foo 的类型推断为 {},即没有属性的对象。因此,你不能在它的属性上添加 bar 或 bas,你可以通过类型断言来避免此问题:你可以通过类型断言来避免此问题:
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';
# 类型断言与类型转换
它之所以不被称为「类型转换」,是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。
# 类型断言可能是有害的
如果你没有按约定添加属性,TypeScript 编译器并不会对此发出错误警告
interface Foo {
bar: number;
bas: string;
}
const foo = {} as Foo;
# 双重断言
类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作:
function handler(event: Event) {
const mouseEvent = event as MouseEvent;
}
然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:
function handler(event: Event) {
const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}
如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any
,编译器将不会报错:
function handler(event: Event) {
const element = (event as any) as HTMLElement; // ok
}
# 类型保护
TypeScript 熟知 JavaScript 中 instanceof
和 typeof
运算符的用法。如果你在一个条件块中使用这些,TypeScript 将会推导出在条件块中的的变量类型。如下例所示,TypeScript 将会辨别 string
上是否存在特定的函数,以及是否发生了拼写错误:
# typeof
function doSome(x: number | string) {
if (typeof x === 'string') {
// 在这个块中,TypeScript 知道 `x` 的类型必须是 `string`
console.log(x.subtr(1)); // Error: 'subtr' 方法并没有存在于 `string` 上, 拼写错误
console.log(x.substr(1)); // ok
}
x.substr(1); // Error: 无法保证 `x` 是 `string` 类型
}
# instanceof
class Foo {
foo = 123;
common = '123';
}
class Bar {
bar = 123;
common = '123';
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
}
if (arg instanceof Bar) {
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
doStuff(new Foo());
doStuff(new Bar());
TypeScript 甚至能够理解 else
。当你使用 if
来缩小类型时,TypeScript 知道在其他块中的类型并不是 if
中的类型:
class Foo {
foo = 123;
}
class Bar {
bar = 123;
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
} else {
// 这个块中,一定是 'Bar'
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
doStuff(new Foo());
doStuff(new Bar());
# in
in
操作符可以安全的检查一个对象上是否存在一个属性,它通常也被作为类型保护使用:
interface A {
x: number;
}
interface B {
y: string;
}
function doStuff(q: A | B) {
if ('x' in q) {
// q: A
} else {
// q: B
q.x = 1 // Error: 类型 B 上不存在属性 x
}
}
# 自定义类型保护
// 仅仅是一个 interface
interface Foo {
foo: number;
common: string;
}
interface Bar {
bar: number;
common: string;
}
// 用户自己定义的类型保护!
function isFoo(arg: Foo | Bar): arg is Foo {
return (arg as Foo).foo !== undefined;
}
// 用户自己定义的类型保护使用用例:
function doStuff(arg: Foo | Bar) {
if (isFoo(arg)) {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
} else {
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
doStuff({ foo: 123, common: '123' });
doStuff({ bar: 123, common: '123' });
# 类型推断
# 定义变量
let foo = 123; // foo 是 'number'
let bar = 'hello'; // bar 是 'string'
foo = bar; // Error: 不能将 'string' 赋值给 `number`
# 函数返回值
返回类型能被 return
语句推断,如下所示,推断函数返回为一个数字:
function add(a: number, b: number) {
return a + b;
}
# 赋值
函数参数类型/返回值也能通过赋值来推断。如下所示,foo
的类型是 Adder
,他能让 foo
的参数 a
、b
是 number
类型。
type Adder = (a: number, b: number) => number;
let foo: Adder = (a, b) => {
a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
return a + b;
};
# 结构化
这些简单的规则也适用于结构化的存在(对象字面量),例如在下面这种情况下 foo
的类型被推断为 { a: number, b: number }
:
const foo = {
a: 123,
b: 456
};
foo.a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型