转自 Typescript 类型进阶 https://juejin.im/post/6876698583426007053#heading-43
这篇文章将通过简单实例介绍开发中常见的问题,希望能帮助你更好理解 Typescript。由于介绍 Typescript
基础知识的文章很多,官方文档本身也很全面,关于 Typescript
的基础本文就不再赘述。
为什么要使用 Typescript?
在正文开始前,想思考一个问题,为什么要使用 Typescript
?
在没有 Typescript
以前,大部分项目都是使用原生 Javascript
开发。而 Javascript
天生是一门“灵活”的语言。“灵活”表现在代码里可以肆无忌惮干任何事,比如拿数字和数组做求和运算,可以调用对象上不存在的方法,给函数传入不符合预期的参数等等,而这些显而易见的问题编码阶段不会有任何错误提示。
const number = 1;
const arr = [1, 2, 3];
console.log(number + arr);
const obj = {};
obj.functionNotExist();
function pow2(value) {
return Math.pow(value, 2);
}
pow2("bazzzzzz");
在大型项目中,一个类型“小改动”可能会导致很多处代码需要跟着调整,光靠肉眼发现很难很难。我们使用 Typescript
的主要目的就是【类型安全】(type-safe),借助类型声明避免程序做错误的事情。
const number = 1;
const arr = [1, 2, 3];
console.log(number + arr); // 运算符“+”不能应用于类型“number”和“number[]”。
const obj = {};
obj.noExistFunction(); // 类型“{}”上不存在属性“noExistFunction”。
function pow(value: number) {
return Math.pow(value, 2);
}
pow("bazzzzzz"); // 类型“string”的参数不能赋给类型“number”的参数。
微妙区别
在 Typescript
中一些关键字,概念存在一些微妙的区别,理解它们有助于编写更好的代码。
any vs unknown
any
表示任意类型,这个类型会逃离 Typescript
的类型检查,和在 Javascript
中一样,any
类型的变量可以执行任意操作,编译时不会报错。unknown
也可以表示任意类型,但它同时也告诉 Typescript
开发者对其也是一无所知,做任何操作时需要慎重。这个类型仅可以执行有限的操作(==、=== 、||、&&、?、!、typeof、instanceof
等等),其他操作需要向 Typescript
证明这个值是什么类型,否则会提示异常。
let foo: any
let bar: unknown
foo.functionNotExist()
bar.functionNotExist() // 对象的类型为 "unknown"。
if (!!bar) { // ==、=== 、||、&&、?、!、typeof、instanceof
console.log(bar)
}
bar.toFixed(1) // Error
if (typeof bar=== 'number') {
bar.toFixed(1) // OK
}
any
会增加了运行时出错的风险,不到万不得已不要使用。表示【不知道什么类型】的场景下使用 unknown
。
{} vs object vs Object
object
表示的是常规的 Javascript
对象类型,非基础数据类型。
declare function create(o: object): void;create({ prop: 0 }); // OKcreate(null); // Errorcreate(undefined); // Errorcreate(42); // Errorcreate("string"); // Errorcreate(false); // Errorcreate({
toString() {
return 3;
},
}); // OK
{}
表示的非 null,非 undefined 的任意类型。
declare function create(o: {}): void;create({ prop: 0 }); // OKcreate(null); // Errorcreate(undefined); // Errorcreate(42); // OKcreate("string"); // OKcreate(false); // OKcreate({
toString() {
return 3;
},
}); // OK
Object
和 {}
几乎一致,区别是 Object
类型会对 Object
原型内置的方法(toString/hasOwnPreperty
)进行校验。
declare function create(o: Object): void;
create({ prop: 0 }); // OK
create(null); // Error
create(undefined); // Error
create(42); // OK
create("string"); // OK
create(false); // OK
create({
toString() {
return 3;
},
}); // Error
如果需要一个对象类型,但对对象的属性没有要求,使用 object
。{}
和 Object
表示的范围太泛尽量不要使用。
type vs interface
两者都可以用来定义类型。
interface
(接口) 只能声明对象类型,支持声明合并(可扩展)。
interface User {
id: string
}
interface User {
name: string
}
const user = {} as User
console.log(user.id);
console.log(user.name);
type
(类型别名)不支持声明合并、行为有点像const
, let
有块级作用域。
type User = {
id: string,
}
if (true) {
type User = {
name: string,
}
const user = {} as User;
console.log(user.name);
console.log(user.id) // 类型“User”上不存在属性“id”。
}
type
更为通用,右侧可以是任意类型,包括表达式运算,以及后面会提到的映射类型等等。
type A = number
type B = A | string
type ValueOf = T[keyof T];
如果你是在开发一个包,模块,允许别人进行扩展就用 interface
,如果需要定义基础数据类型或者需要类型运算,使用 type
。
enum vs const enum
默认情况下 enum
会被编译成 Javascript
对象,并且可以通过 value
反向查找。
enum ActiveType {
active = 1,
inactive = 2,
}
function isActive(type: ActiveType) {}
isActive(ActiveType.active);
// ============================== compile result:
// var ActiveType;
// (function (ActiveType) {
// ActiveType[ActiveType["active"] = 1] = "active";
// ActiveType[ActiveType["inactive"] = 2] = "inactive";
// })(ActiveType || (ActiveType = {}));
// function isActive(type) { }
// isActive(ActiveType.active);
ActiveType[1]; // OK
ActiveType[10]; // OK!!!
cosnt enum
默认情况下不会生成 Javascript
对象而是把使用到的代码直接输出 value
,不支持 value
反向查找。
const enum ActiveType {
active = 1,
inactive = 2,
}
function isActive(type: ActiveType) {}
isActive(ActiveType.active);
// ============================== compile result:
// function isActive(type) { }
// isActive(1 /* active */);
ActiveType[1]; // Error
ActiveType[10]; // Error
enum
中括号索引取值的方式容易出错,相对 enum
,const enum
是更安全的类型。
脚本模式和模块模式
Typescript
存在两种模式,脚本模式(Script
)一个文件对应一个 html
的 script
标签,模块模式(Module
)下一个文件对应一个 Typescript
的模块。区分的逻辑是,文件内容包不包含 import
或者 export
关键字。
了解这两种模式的区别有助于理解编写演示代码时的一些“怪异”现象。
脚本模式下,所有变量定义,类型声明都是全局的,多个文件定义同一个变量会报错,同名 interface
会进行合并。而模块模式下,所有变量定义,类型声明都是模块内有效的。
两种模式在编写类型声明时也有区别,例如脚本模式下直接 declare var GlobalStore
即可为全局对象编写声明。
GlobalStore.foo = "foo";
GlobalStore.bar = "bar"; // Error
declare var GlobalStore: {
foo: string;
};
而模块模式下,要为全局对象编写声明需要 declare global
GlobalStore.foo = "foo";
GlobalStore.bar = "bar";
declare global {
var GlobalStore: {
foo: string;
bar: string;
};
}
export {}; // export 关键字改变文件的模式