TS 官网文档阅读之一:TypeScript 对于 JavaScript 开发者
TypeScript 对于 JavaScript 来说有着一种不寻常的关系,TypeScript 除了提供所有 JavaScript 具有的特性外,并且在此之上,还带来了 TypeSctipt 的类型系统。
例如,JavaScript 提供了原语,像是 string
和 number
,但是并没有过多的去检查这些类型,但是 TypeScript 做到了。
这表明你现有工作区的 JavaScript 代码其实本质上也是 TypeScript 代码,得益于 TypeScript,那些不合理的代码,会自动高亮出来,继而减少 bug 发生的几率。
这个教程提供了一个关于 TypeScript 的概述,主要关注于它的类型系统。
类型推断
TypeScript 了解 JavaScript 语言,并且会在很多场景下为你生成类型。例如,当你创建一个变量并且给其赋值一个指定值以后,TypeScript 将会用这个值的类型作为推断出的类型。
let helloWorld = "Hello World"; // let helloWorld: string
通过了解 JavaScript 的运作,TypeScript 可以构建一个基于 JavaScript 编码的类型系统,并且具有类型机制。这个类型检测系统,无须额外添加额外字符就可以明确代码的类型。这就是上面例子中的 TypeScript 是如何知道 helloWorld
是一个 string
类型的。
你可能已经通过 Visual Studio Code编辑器编写过 JavaScript 代码了,也感受过编辑器的自动补全功能了,Visual Studio Code 编辑器可以在 TypeScript 引擎下,让你更加轻松的将 TypeScript 引入到 JavaScript 中去。
定义类型
在 Javascript 中,你可以使用丰富的设计模式,可是,一些设计模式反而使得类型的自动推断变得复杂,(例如,动态规划类的模式),考虑到这种情况,TypeScript 提供了一个 JavaScript 语言的扩展,提供了一个地方专门用来让 TypeScript 告诉你这个类型应该是什么。
例如,我们可以创建一个对象,推断其类型包含了 name: string
和 id: number
,你可以像下面这样写:
const user = {
name: "Hayess",
id: 0
}
你可以通过一个 interface
声明来明确的描述这个对象的外形。
interface User {
name: string;
id: number;
}
然后你可以声明这个 JavaScript 对象符合你新定义的 interface
,通过类似在变量后声明 : TypeName
的句法来说明这一点。
const user: User = {
name: "Hayess",
id: 0
}
如果你提供的对象没能匹配上接口定义的话,TypeScript 将会提示报错:
interface User {
name: string;
id: number;
}
const user: User = {
username: "Hayes", // Type '{ username: string; id: number; }' is not assignable to type 'User'. Object literal may only specify known properties, and 'username' does not exist in type 'User'.
id: 0
}
由于 JavaScript 提供了类和面向对象编程,所以在 TypeScript 中,你可以用一个 interface 声明在类上面。
interface User {
name: string;
id: number;
}
class UserAccount {
name: string;
id: number;
construtor(name: string, id: number) {
this.name = name;
this.id = id
}
}
const user: User = new UserAccount("Murphy", 1);
你可以使用 interface 来标注函数参数和返回值类型
function getAdminUser(): User {
// ...
}
function deleteUser(user: User) {
// ...
}
在 JavaScript 中已经存在一小部分的可用的原始类型:boolean
,bigint
,null
,number
,string
,sumbol
,undefined
,这些也都可以用在 interface 中去,TypeScript 继承了这些,但不仅限于这些,甚至更多,例如 any
(允许任意类型),unknown
(确保使用该类型的人声明该类型是什么,貌似有点废话了,翻译水平可能不过关,见谅哈😂),never
(这个类型代表这个不可能存在),以及 void
(当一个函数返回 undefined
或者没有返回值)。
可以看到,有两种定义类型的语法,interface
和 type
,你应该更倾向于使用 interface
,只有当需要定于具体的属性时候,使用 type
。
组合类型
在 TypeScript 中,你可以通过混合一些简单的类型来创建复杂的类型,有两种流行的方式来这么做,一个是使用联合类型
,一个是使用泛型
。
联合类型
使用联合类型,你可以编写一个包含一到多个类型的声明,例如,可以通过使用 true
和 false
两个简单类型,构建 boolean
这个类型。
type MyBool = true | fasle;
注释:如果你悬浮在 MyBool
上面,你将会看到它被归类为 boolean
,这就是结构化类型系统的一个特征。
更多内容如下。
一个比较流行的联合类型的用例,就是使用一个字符或者数字的集合来规定哪些是被允许的:
type WindowStates = "open" | "closed" | "minimized";
type LockStates = "locked" | "unlocked";
type PositiveOddNumberUnderTen = 1 | 3 | 5 | 7 | 9;
联合类型同样提供了定义不同类型的方式,例如,可以给函数参数同时设置 array
和 string
类型:
function getLength(obj: string | string[]) {
return obj.length;
}
可以通过使用 typeof
来获取一个变量的类型。
类型 | 断言 |
---|---|
string | typeof s === "string" |
number | typeof n === "number" |
boolean | typeof b === "boolean" |
undefined | typeof undefined === "undefined" |
function | typeof f === "function" |
array | Array.isArray(a) |
例如,你可以使用一个函数根据传入的是字符串还是数组来返回不同的类型:
function wrapInArray(obj: string | string[]) {
if (typeof obj === "string") { // (parameter) obj: string
return [obj];
}
return obj; // (parameter) obj: string[]
}
泛型
泛型提供了一个变量来定义类型,一个常见的用例就是数组。一个没有定义泛型的数组,可以包含任意类型的数据,一个定义了泛型的数组,可以清晰地描述出它里面包含的数据类型。
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;
也可以通过泛型定义自己的类型
interface BackPack<Type> {
add: (obj: Type) => void;
get: () => Type;
}
// This line is a shortcut to tell TypeScript there is a
// constant called `backpack`, and to not worry about where it came from.
declare const backpack: Backpack<string>;
// object is a string, because we declared it above as the variable part of Backpack.
const object = backpack.get();
// Since the backpack variable is a string, you can't pass a number to the add function.
backpack.add(23); // Argument of type 'number' is not assignable to parameter of type 'string'.
结构化类型系统
TypeScript 的一个核心原则就是类型检测只依赖于值的结果,这个有时候也被称为鸭子类型
或者结构化类型
。
在一个结构化类型系统中,如果两个对象有着相同的外形,那么就可以被认为具有相同的类型。
interface Point {
x: number;
y: number;
}
function logPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);
可以看到,变量 point
并没有被定义成 Point
类型,可是,TypeScript 比较了变量 point
的形状和 接口 Point
的类型校验之后,得出它们具有相同类型,所以代码通过校验。
这个形状匹配
只需要对象字段的子集匹配就可以了。
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"
const color = { hex: "#187ABF" };
logPoint(color); // Argument of type '{ hex: string; }' is not assignable to parameter of type 'Point'. Type '{ hex: string; }' is missing the following properties from type 'Point': x, y
类和对象如何符合形状之间没有区别
class VirtualPoint {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const newVPoint = new VirtualPoint(13, 56);
logPoint(newVPoint); // logs "13, 56"
如果对象或类具有所有必需的属性,TypeScript 会说它们匹配,而不管实现细节如何。
转载自:https://juejin.cn/post/7220770687311986748