共计 12117 个字符,预计需要花费 31 分钟才能阅读完成。
- 阐明:目前网上没有 TypeScript 最新官网文档的中文翻译,所以有了这么一个翻译打算。因为我也是 TypeScript 的初学者,所以无奈保障翻译百分之百精确,若有谬误,欢送评论区指出;
- 翻译内容:暂定翻译内容为 TypeScript Handbook,后续有空会补充翻译文档的其它局部;
- 我的项目地址:TypeScript-Doc-Zh,如果对你有帮忙,能够点一个 star ~
本章节官网文档地址:Everyday Types
一般类型
在这一章中,咱们的内容会波及到 JavaScript 代码中最常见的一些数据类型,同时也会解释这些类型在 TypeScript 中的对应形容形式。本章节并不会详尽介绍所有类型,在后续章节中咱们还会介绍更多命名和应用其它类型的办法。
类型不仅能够呈现在类型注解中,还能够呈现在许多其它中央。在学习类型自身的同时,咱们也会学习如何在某些中央应用这些类型去组成新的构造。
首先,咱们先来回顾一下编写 JavaScript 或者 TypeScript 代码时最根底和最罕用的类型。它们稍后将成为更简单类型的外围组成部分。
原始类型:string
、number
和 boolean
JavaScript 有三种很罕用的原始类型:string
、number
和 boolean
。每一种类型在 TypeScript 中都有绝对应的类型。正如你所料,它们的名字就和应用 JavaScript 的 typeof
运算符失去的字符串一样:
string
示意相似"Hello, world!"
这样的字符串值number
示意相似42
这样的数值。对于整数,JavaScript 没有非凡的运行时值,所以也就没有int
或者float
类型 —— 所有的数字都是number
类型boolean
示意布尔值true
和false
类型名
String
、Number
和Boolean
(大写字母结尾)也是非法的,但它们指的是在代码中很少呈现的内建类型。请始终应用string
、number
和boolean
数组
为了示意相似 [1,2,3]
这样的数组类型,你能够应用语法 number[]
。这种语法也能够用于任意类型(比方 string[]
示意数组元素都是字符串类型)。它还有另一种写法是 Array<number>
,两者成果是一样的。在后续解说泛型的时候,咱们会再具体介绍 T<U>
语法。
留神
[number]
和一般数组不同,它示意的是元组
any
TypeScript 还有一种非凡的 any
类型。当你不想要让某个值引起类型查看谬误的时候,能够应用 any
。
当某个值是 any
类型的时候,你能够拜访它的任意属性(这些属性也会是 any
类型),能够将它作为函数调用,能够将它赋值给任意类型的值(或者把任意类型的值赋值给它),或者是任何语法上合规的操作:
let obj: any = {x: 0};
// 上面所有代码都不会引起编译谬误。应用 any 将会疏忽类型查看,并且假设了
// 你比 TypeScript 更理解以后环境
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
当你不想要写一长串类型让 TypeScript 确信某行代码没问题的时候,any
类型很管用。
noImplicitAny
当你没有显式指定一个类型,同时 TypeScript 也无奈从上下文中进行类型推断的时候,编译器会默认将其作为 any
类型解决。
不过,通常你会防止这种状况的产生,因为 any
是会绕过类型查看的。启用 noImplicitAny 配置项能够将任意隐式推断失去的 any
标记为一个谬误。
变量的类型注解
当你应用 const
、var
或者 let
申明变量的时候,你能够选择性地增加一个类型注解以显式指定变量的类型:
let myName: string = 'Alice';
TypeScript 没有采纳相似
int x = 0
这样“在表达式右边申明类型”的格调。类型注解总是跟在要申明类型的货色前面。
不过,在大多数状况下,注解并不是必须的。TypeScript 会尽可能地在你的代码中主动进行类型推断。举个例子,变量的类型是基于它的初始值推断进去的:
// 不须要增加类型注解 —— myName 会被主动推断为 string 类型
let myName = 'Alice';
少数状况下,你不须要刻意去学习类型推断的规定。如果你还是初学者,请尝试尽可能少地应用类型注解 —— 你可能会诧异地发现,TypeScript 齐全了解所产生的事件所须要的注解是如此之少。
函数
函数是 JavaScript 中传递数据的次要形式。TypeScript 容许你指定函数的输出和输入的类型。
参数类型注解
当你申明一个函数的时候,你能够在每个参数前面增加类型注解,从而申明函数能够承受什么类型的参数。参数的类型注解跟在每个参数名字的前面:
// 参数类型注解
function greet(name: string){console.log('Hello,' + name.toUpperCase() + '!!');
}
当函数的某个参数有类型注解的时候,TypeScript 会对传递给函数的实参进行类型查看:
// 如果执行,会有一个运行时谬误!greet(42);
// Argument of type 'number' is not assignable to parameter of type 'string'.
即便没有给参数增加类型注解,TypeScript 也会查看你传递的参数的个数是否正确
返回值类型注解
你也能够给返回值增加类型注解。返回值类型注解呈现在参数列表前面:
function getFavourNumber(): number {return 26;}
和变量的类型注解一样,通常状况下咱们不须要给返回值增加一个类型注解,因为 TypeScript 会基于 return
语句推断出函数返回值的类型。上述例子中的类型注解不会扭转任何事件。一些代码库会显式指定返回值的类型,这可能是出于文档编写的须要,或者是为了避免意外的批改,或者只是集体爱好。
匿名函数
匿名函数和函数申明有点不同。当一个函数呈现在某个中央,且 TypeScript 能够推断它是如何被调用的时候,该函数的参数会被主动调配类型。
比方:
// 这里没有类型注解,但 TypeScript 仍能在后续代码找出 bug
const names = ["Alice", "Bob", "Eve"];
// 基于上下文推断匿名函数参数的类型
names.forEach(function (s) {console.log(s.toUppercase());
^^^^^^^^^^^^
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// 对于箭头函数,也能够正确推断
names.forEach((s) => {console.log(s.toUppercase());
^^^^^^^^^^^^^
//Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
即便这里没有给参数 s
增加类型注解,TypeScript 也能够基于 forEach
函数的类型,以及对于 name
数组类型的推断,来决定 s
的类型。
这个过程叫做 上下文类型推断,因为函数调用时所处的上下文决定了它的参数的类型。
和推断规定相似,你不须要刻意学习这个过程是怎么产生的,但明确这个过程的确会产生之后,你天然就分明什么时候不须要增加类型注解了。稍后咱们会看到更多的例子,理解到一个值所处的上下文是如何影响它的类型的。
对象类型
除了原始类型之外,最常见的类型就是对象类型了。它指的是任意蕴含属性的 JavaScript 值。要定义一个对象类型,只须要简略地列举它的属性和类型即可。
举个例子,上面是一个承受对象类型作为参数的函数:
// 参数的类型注解是一个对象类型
function printCoord(pt: { x: number; y: number}) {console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({x: 3, y: 7});
这里,咱们为参数增加的类型注解是一个蕴含 x
和 y
两个属性(类型都是 number
)的对象。你能够应用 ,
或者 ;
分隔每个属性,最初一个属性的分隔符可加可不加。
每个属性的类型局部同样也是可选的,如果你没有指定类型,那么它会采纳 any
类型。
可选属性
对象类型也能够指定某些或者全副属性是可选的。你只须要在对应的属性名前面增加一个 ?
即可:
function printName(obj: { first: string; last?: string}) {// ...}
// 上面两种写法都行
printName({first: "Bob"});
printName({first: "Alice", last: "Alisson"});
在 JavaScript 中,如果你拜访了一个不存在的属性,你将会失去 undefined
而不是一个运行时谬误。因而,在你读取一个可选属性的时候,你须要在应用它之前查看它是否为 undefined
。
function printName(obj: { first: string; last?: string}) {
// 如果 obj.last 没有对应的值,可能会报错!console.log(obj.last.toUpperCase());
// Object is possibly 'undefined'.
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
// 上面是应用古代 JavaScript 语法的另一种平安写法:console.log(obj.last?.toUpperCase());
}
联结类型
TypeScript 的类型零碎容许你基于既有的类型应用大量的运算符创立新的类型。既然咱们曾经晓得了如何编写根本的类型,是时候开始用一种乏味的形式将它们联合起来了。
定义一个联结类型
第一种联合类型的形式就是应用联结类型。联结类型由两个或者两个以上的类型组成,它代表的是能够取这些类型中任意一种类型的值。每一种类型称为联结类型的成员。
咱们来编写一个能够解决字符串或者数字的函数:
function printId(id: number | string) {console.log("Your ID is:" + id);
}
// OK
printId(101);
// OK
printId("202");
// 报错
printId({myID: 22342});
// Argument of type '{myID: number;}' is not assignable to parameter of type 'string | number'.
// Type '{myID: number;}' is not assignable to type 'number'.
应用联结类型
提供一个匹配联结类型的值非常简单 —— 只须要提供一个与联结类型某个成员相匹配的类型即可。如果有一个值是联结类型,你要怎么应用它呢?
TypeScript 会限度你对联结类型能够采取的操作,仅当该操作对于联结类型的每个成员都失效的时候,操作才会失效。举个例子,如果你有联结类型 string | number
,那么你将无奈应用只能由 string
调用的办法:
function printId(id: number | string) {console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
}
解决方案就是在代码中去收窄联结类型,这和没有应用类型注解的 JavaScript 的做法一样。当 TypeScript 可能基于代码构造推断出一个更具体的类型时,就会产生收窄。
举个例子,TypeScript 晓得只有 string
类型的值应用 typeof
之后会返回 "string"
:
function printId(id: number | string) {if (typeof id === "string") {
// 在这个分支中,id 的类型是 string
console.log(id.toUpperCase());
} else {
// 这里,id 的类型是 number
console.log(id);
}
}
另一个例子是应用相似 Array.isArray
这样的函数:
function welcomePeople(x: string[] | string) {if (Array.isArray(x)) {// 这里,x 是 string[]
console.log("Hello," + x.join("and"));
} else {
// 这里,x 是 string
console.log("Welcome lone traveler" + x);
}
}
留神,在 else
分支中,咱们不须要做额定的判断 —— 如果 x
不是 string[]
,那它就肯定是 string
。
有时候,联结类型的所有成员可能存在共性。举个例子,数组和字符串都有 slice
办法。如果一个联结类型的每个成员都有一个公共的属性,那么你能够不须要进行收窄,间接应用该属性:
// 返回值会被推断为 number[] | string
function getFirstThree(x: number[] | string) {return x.slice(0, 3);
}
联结类型的各个类型的属性存在交加,你可能会感觉有点困惑。实际上这并不让人意外,“联结”这个名词来自于类型实践。联结类型
number | string
是由每个类型的值的联结组成的。假如给定两个汇合以及各自对应的事实,那么只有事实的交加能够利用于汇合的交加自身。举个例子,有一个屋子的人都很高,而且戴帽子,另一个屋子的人都是西班牙人,而且也戴帽子,那么两个屋子的人放到一起,咱们能够失去的惟一事实就是:每个人必定都戴着帽子。
类型别名
目前为止,咱们都是在类型注解中间接应用对象类型或者联结类型的。这很不便,但通常状况下,咱们更心愿通过一个独自的名字屡次援用某个类型。
类型别名就是用来做这个的 —— 它能够作为指代任意一种类型的名字。类型别名的语法如下:
type Point = {
x: number;
y: number;
};
// 成果和之前的例子齐全一样
function printCoord(pt: Point) {console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({x: 100, y: 100});
不止是对象类型,你能够给任意一种类型应用类型别名。举个例子,你能够命名联结类型:
type ID = number | string;
留神,别名就只是别名而已 —— 你不能应用类型别名去创立同一类型的不同“版本”。当你应用别名的时候,成果就和你间接编写理论的类型一样。换句话说,代码看起来是不非法的,但在 TypeScript 里这是没问题的,不论是别名还是理论类型,都指向同一个类型:
type UserInputSanitizedString = string;
function sanitizeInput(str: string): UserInputSanitizedString {return sanitize(str);
}
// 创立一个输出
let userInput = sanitizeInput(getInput());
// 能够从新给它赋值一个字符串
userInput = "new input";
接口
接口申明是另一种命名对象类型的形式:
interface Point {
x: number;
y: number;
}
function printCoord(pt: Point) {console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({x: 100, y: 100});
就和下面应用类型别名一样,这个例子也能够失常运行,它的成果和间接应用一个匿名对象类型一样。TypeScript 只关怀咱们传递给 printCoord
的值的构造 —— 它只关怀这个值是否有冀望的属性。正是因为这种只关注类型的构造和能力的特点,所以咱们说 TypeScript 是一个结构性的、类型性的类型零碎。
类型别名和接口的区别
类型别名和接口很类似,少数状况下你能够任意抉择其中一个去应用。接口的所有个性简直都能够在类型别名中应用。两者要害的区别在于类型别名无奈再次“关上”并增加新的属性,而接口总是能够拓展的。
// 接口能够自在拓展
interface Animal {name: string}
interface Bear extends Animal {honey: boolean}
const bear = getBear()
bear.name
bear.honey
// 类型别名须要通过交加进行拓展
type Animal = {name: string}
type Bear = Animal & {honey: boolean}
const bear = getBear();
bear.name;
bear.honey;
// 向既有的接口增加新的属性
interface Window {title: string}
interface Window {ts: TypeScriptAPI}
const src = 'const a ="Hello World"';
window.ts.transpileModule(src, {});
// 类型别名一旦创立,就不能再批改了
type Window = {title: string}
type Window = {ts: TypeScriptAPI}
// Error: Duplicate identifier 'Window'
在稍后的章节中,你会学到更多对于这方面的常识,所以当初还不太了解也没关系。
- 在 TypeScript 4.2 版本之前,类型别名的名字可能会呈现在报错信息中,有时会代替等效的匿名类型(可能须要,也可能不须要)。而接口的名字则始终呈现在报错信息中
- 类型别名无奈进行申明合并,但接口能够
- 接口只能用于申明对象的形态,无奈为原始类型命名
- 在报错信息中,接口的名字将始终以原始模式呈现,但只限于它们作为名字被应用的时候
大多数状况下,你能够依据集体爱好抉择其中一种应用,TypeScript 也会通知你它是否须要应用另一种申明形式。如果你喜爱启发式,那你能够应用接口,等到须要应用其余个性的时候,再应用类型别名。
类型断言
有时候,你会比 TypeScript 更理解某个值的类型。
举个例子,如果你应用 document.getElementById
,那么 TypeScript 只晓得这个调用会返回某个 HTMLElement
,但你却晓得你的页面始终存在一个给定 ID 的 HTMLCanvasElement
。
在这种状况下,你能够应用类型断言去指定一个更具体的类型:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
就像类型注解一样,编译器最终会移除类型断言,保障它不会影响到代码的运行时行为。
你也能够应用等效的尖括号语法(前提是代码不是在一个 .tsx
文件中):
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
记住:因为编译期间会移除类型断言,所以不存在和类型断言相干的运行时查看。即便类型断言是谬误的,也不会抛出异样或者产生
null
TypeScript 只容许断言之后的类型比之前的类型更具体或者更不具体。这个规定能够防止出现上面这样“不可能存在的”强制类型转换:
const x = "hello" as number;
// 类型 "string" 到类型 "number" 的转换可能是谬误的,因为两种类型不能充沛重叠。如果这是无意的,请先将表达式转换为 "unknown"
有时候,这个规定可能过于激进了,会妨碍咱们进行更简单的无效转换操作。如果是这样,那么能够应用两步断言,先断言为 any
(或者 unknown
,稍后再介绍),再断言为冀望的类型:
const a = (expr as any) as T;
字面量类型
除了通用的 string
和 number
类型之外,咱们也能够将具体的字符串或者数字看作一种类型。
怎么了解呢?其实咱们只须要思考 JavaScript 申明变量的不同形式即可。var
和 let
申明的变量都能够批改,但 const
不行。这种特点反映在 TypeScript 是如何为字面量创立类型的。
let changingString = "Hello World";
changingString = "Olá Mundo";
// 因为 changingString 能够示意任意可能的字符串,这是 TypeScript
// 在类型零碎中形容它的形式
changingString;
^^^^^^^^^^^^^^
// let changingString: string
let changingString: string
const constantString = "Hello World";
// 因为 constantString 只能示意一种可能的字符串,所以它有一个
// 字面量类型的示意模式
constantString;
^^^^^^^^^^^^^^^
// const constantString: "Hello World"
只是独自应用的话,字面量类型的用途并不大:
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.
下面的例子中,变量只有一个可能的值,这是没有意义的!
然而通过将字面量类型联合为联结类型,你能够示意一个更有实用价值的概念 —— 举个例子,申明一个只承受某些固定值的函数:
function printText(s: string, alignment: "left" | "right" | "center") {// ...}
printText("Hello, world", "left");
printText("G'day, mate","centre");
^^^^^^
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
数值型字面量类型也同理:
function compare(a: string, b: string): -1 | 0 | 1 {return a === b ? 0 : a > b ? 1 : -1;}
当然,联结类型中也能够蕴含非字面量类型:
interface Options {width: number;}
function configure(x: Options | "auto") {// ...}
configure({width: 100});
configure("auto");
configure("automatic");
^^^^^^^^^^
// Argument of type '"automatic"' is not assignable to parameter of type 'Options |"auto"'.
还有一种字面量类型:布尔值字面量。只有两种布尔值字面量类型,也就是 true
和 false
。boolean
类型自身其实就是联结类型 true | false
的一个别名。
字面量推断
当你初始化一个变量为某个对象的时候,TypeScript 会假设该对象的属性稍后可能会发生变化。比方上面的代码:
const obj = {counter: 0};
if (someCondition) {obj.counter = 1;}
TypeScript 不感觉将之前值为 0 的属性赋值为 1 是一个谬误。另一种了解角度是,obj.counter
必须是 number
类型,而不是 0,因为类型能够用来决定读写行为。
对于字符串也同理:
const req = {url: "https://example.com", method: "GET"};
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
(译者注:这里的 handleRequest
签名为 (url: string, method: "GET" | "POST") => void
)
在下面的例子中,req.method
被推断为 string
,而不是 "GET"
。因为在创立 req
和调用 handleRequest
之间可能会执行其它代码,req.method
兴许会被赋值为相似 "GUESS"
这样的字符串,因而 TypeScript 会认为这样的代码是存在谬误的。
有两种形式能够解决这个问题:
-
通过增加类型断言扭转类型的推断后果:
// 办法一:const req = {url: "https://example.com", method: "GET" as "GET"}; // 办法二:handleRequest(req.url, req.method as "GET");
办法一示意“我无意让 req.method
始终采纳字面量类型 "GET"
”,从而阻止后续将其赋值为其它字符串;办法二示意“出于某种理由,我确信 req.method
的值肯定是“GET”
”。
-
你还能够应用
as const
将整个对象转化为字面量类型:const req = {url: "https://example.com", method: "GET"} as const; handleRequest(req.url, req.method);
as const
后缀和const
的成果很像,但用于类型零碎中。它能够确保对象的所有属性都被赋予了一个字面量类型,而不是采纳相似string
或者number
这样较为通用的类型。
null
和 undefined
JavaScript 中有两个原始值用于示意短少的或者没有初始化的值:null
和 undefined
。
TypeScript 对应地也有两个名字和它们一样的类型。它们的行为取决于你是否启用了 strictNullChecks 选项。
禁用 strictNullChecks
禁用 strictNullChecks 选项之后,你依然能够失常拜访可能为 null
和 undefined
的值,这两个值也能够被赋值给任何一种类型。这种行为表现和短少空值查看的语言(比方 C#、Java)很像。短少对这些值的查看可能是大量 bug 的起源,在可行的前提下,咱们举荐开发者始终启用 strictNullChecks 选项。
启用 strictNullChecks
启用 strictNullChecks 选项之后,当一个值是 null
或者 undefined
的时候,你须要在应用该值的办法或者属性之前首先对其进行查看。就和应用可选属性之前先查看它是否为 undefined
一样,咱们能够应用类型收窄去查看某个值是否可能为 null
:
function doSomething(x: string | null) {if (x === null) {// do nothing} else {console.log("Hello," + x.toUpperCase());
}
}
非空值断言操作符(!
后缀)
TypeScript 也提供了一种非凡的语法,能够在不显式进行查看的状况下,将 null
和 undefined
从类型中排除。在任意表达式前面增加后缀 !
,能够无效地断言某个值不可能为 null
或者 undefined
:
function liveDangerously(x?: number | null) {
// 不会报错
console.log(x!.toFixed());
}
和其它的类型断言一样,非空值断言也不会扭转代码的运行时行为,所以切记:仅在你确定某个值不可能为 null
或者 undefined
的时候,才去应用 !
。
枚举
枚举是 TypeScript 增加到 JavaScript 中的一项个性。它容许形容一个值,该值能够是一组可能的命名常量中的一个。与大多数的 TypeScript 个性不同,枚举不是在类型层面增加到 JavaScript 中的,而是增加到语言自身和它的运行时中。正因如此,你应该理解这个个性的存在,但除非你确定,否则你可能须要推延应用它。你能够在枚举援用页面中理解到无关枚举的更多信息。
其它不常见的原始类型
值得一提的是,JavaScript 的其它原始类型在类型零碎中也有对应的示意模式。不过在这里咱们不会深刻进行探讨。
BigInt
ES2020 引入了 BigInt
,用于示意 JavaScript 中十分大的整数:
// 通过 BigInt 函数创立大整数
const oneHundred: bigint = BigInt(100);
// 通过字面量语法创立大整数
const anotherHundred: bigint = 100n;
你能够在 TypeScript 3.2 公布日志 中理解到对于 BigInt 的更多信息。
symbol
在 JavaScript 中,咱们能够通过函数 Symbol()
创立一个全局惟一的援用:
const firstName = Symbol("name");
const secondName = Symbol("name");
if (firstName === secondName) {// 此条件将始终返回 "false",因为类型 "typeof firstName" 和 "typeof secondName" 没有重叠。}
你能够在 Symbol 援用页面 理解到更多相干信息。