let a; // 这是什么类型?
你可能觉得这个变量名取得不好,没有意义,比如 let appleCount
就可以猜到它是一个数字。那我换一个栗子:
let requestOptions; // 这是什么类型?
这个变量名应该很明确了,这是一个请求的参数,而且它很可能是一个对象(也许是一个字符串?),那么下一个问题就是,这个对象里可以放什么属性?每个属性的值是什么类型?哪些属性是可选的?我们可以猜测也许有一个属性是 url
,一个属性是 method
,还有一个属性是 data
等等,我们试一下:
request({
url: 'http://foo.bar',
method: 'post',
data: 'Hello'
})
这段代码的执行结果很可能不符合你的预期,因为:
url
,而是 host
、path
和 query
method
不是 post
,而是大写的 POST
data
,而是 body
面对报错或者不符合预期的结果,你能做的就是去找到这个函数的文档,更差的情况是,这个函数没有文档,那你只能看源码,而看源码势必会花费很多精力,还不一定看得明白。
还是以刚才的请求函数为例子,你能猜出这个 response
是什么类型吗?
const response = request({
url: 'http://foo.bar',
method: 'post',
data: 'Hello'
})
我的直觉告诉我,它是一个 Promise
,事实上很可能就是这样,但是也有可能是另一种情况 -- 回调函数 -- Node.js 的默认情况,如果不看文档,你并不知道到底是 Promise
还是回调函数,或者两者均可,或者甚至两者都不是,更差的情况,如果文档都没有,那你又双叒叕只能看源码了。
以[这段代码]为例子。
interface RequestOptions {
url: string;
method: 'GET' | 'POST',
data?: string;
}
interface ResponseData {
statusCode: number;
header: Record<string, string | number>;
body?: string;
}
async function request(options: RequestOptions): Promise<ResponseData> {
return {
statusCode: 200,
header: {
'Content-Type': 'text/plain'
},
body: 'Hello'
}
}
// 如果用字符串调用 request 函数
const response0 = request('http://foo.bar');
// 如果我们用错误的选项进行调用
const response1 = request({
host: 'foo.bar',
protocol: 'http',
path: '/index.html'
});
// 如果我们写错了某些参数
const response2 = request({
url: 'http://foo.bar/index.html',
method: 'get'
});
// 如果我们缺少了某些必须的参数
const response3 = request({
url: 'http://foo.bar',
data: 'Hello'
});
// 如何知道返回值的类型
const response4 = request({
url: 'http://foo.bar/index.html',
method: 'GET'
})
response4.then(res => console.log(res.body))
你可能也听说过 Flow,或者是 CoffeeScript,但是为什么要选择 TypeScript 呢?TypeScript 有几个特点非常受大家的欢迎:
TypeScript
是 JavaScript
的超集,意思就是 JavaScript 就是合法的 TypeScript,只是没有类型JavaScript
TypeScript
的类型系统是结构子类型,而非名义类型TypeScript
可以和 JavaScript
共存,甚至可以检查 JavaScript
TypeScript
可以编译到任意版本的 JavaScript
JavaScript
编写的包,也可以通过类型文件获得 TypeScript
的支持TypeScript
类型是描述某个值的类型或结构的注解 (annotation)(之所以称为注解是因为没有类型也是合法的 TypeScript
,因此类型就像看书时写在某个词或某句话旁边的注解),因此在 TypeScript
中,类型被称为“类型注解” (type annotation)。
number
string
object
true
在 JavaScript 中 true
是一个布尔值,那它能不能是一个类型呢?1
在 JavaScript 中 1
是一个数值,那它能不能是一个类型呢?'foo'
在 JavaScript 中 'foo'
是一个字符串值,那它能不能是一个类型呢?undefined
在 JavaScript 中 undefined
表示空值,那它能不能是一个类型呢?null
在 JavaScript 中 null
表示这应该是一个对象,但现在是空值,那它能不能是一个类型呢?前三个 number``string``object
毫无疑问可以作为类型使用,那 true``1``'foo'
能不能作为类型使用呢?在 TypeScript 中是可以的,不仅仅是 true``1``'foo'
,任何原始值类型都可以作为类型使用,也就是任意数字、任意字符串、任意布尔值,甚至是任意的 symbol
(通过调用 Symbol()
得到的)也可以作为类型(它的类型是 unique symbol
,以后会提到)使用。一个原始值作为类型使用的时候,就表示拥有该类型的值只能被赋值为该原始值,例如:
type Foo = true;
let foo: Foo = true; // ok
let bar: Foo = 1; // Error: type '1' is not assignable to type 'true'
所有 JavaScript 中的原始值类型都是 TypeScript 的原始类型,包括:
boolean
number
string
undefined
symbol
bigint
ES2020 新增和 JavaScript 一样,TypeScript 的 boolean
类型只有两个值,true
和 false
,分别代表真和假。
let passed: boolean = true;
TypeScript 中的数字类型和 JavaScript 中的一样,都是符合 IEEE754 的双精度浮点数,数字类型可以被赋值为整数或者小数,支持十进制、二进制、八进制和十六进制:
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
字符串类型是另一种非常重要的基础类型,几乎所有程序都会用到字符串,TypeScript 中的字符串和 JavaScript 中的一样,你可以使用单引号 ('
) 或者双引号 ("
) 来定义字符串的边界,两者没有任何区别:
let color: string = "blue";
color = 'red';
当然,你也可以使用 ES2015 中新增的模板字符串来创建多行字符串,还可以在里面使用插值:
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ fullName }.
I'll be ${ age + 1 } years old next month.`;
小提示:模板字符串是可以嵌套的,也就是说在插值里面可以再次使用模板字符串,但是一般不建议这么用,因为代码层级太深不直观,不太容易被理解。
在 TypeScript 中,undefined
和 null
分别有自己对应的类型,也就是 undefined
和 null
,这个类型不太常用,因为这个类型只能被赋值为他们对应的值:
// 不能被赋值为其他类型
let u: undefined = undefined;
let n: null = null;
默认情况下,undefined
和 null
是任何类型的子类型,因此可以赋值给任意类型,比如 number
:
let num: number = null; // OK
但是如果开启了 \--strictNullChecks
时,undefined
和 null
只能被赋值给 any
类型和他们各自对应的类型(唯一的例外是 undefined
可以被赋值给 void
类型),这个 flag 可以避免非常多的常见错误,如果你想让某个值可以被赋值为 undefined
或者 null
或者 string
,你可以使用联合类型 undefined | null | string
。
联合类型会在后面的章节介绍。
symbol
是 ES2015 中引入的一个新的原始值类型,和 number
、string
一样。可以通过调用 Symbol() 构造函数来生成一个新的 symbol
,注意,symbol
是一个原始值类型而非对象,因此调用时不能在前面加上 new
运算符:
let sym1 = Symbol();
let sym2 = Symbol('Description'); // optional parameter key to describe a symbol
每一个 symbol 都是独一无二且不可变的:
let sym3 = Symbol('key');
let sym4 = Symbol('key');
sym3 === sym4; // false
symbol
可以和 string
一样作为对象的 key:
const sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
symbol
还可以用在计算属性中,作为类的方法名或者对象的方法名:
const getClassNameSymbol = Symbol();
class C {
[getClassNameSymbol]() {
return "C";
}
}
let c = new C();
let className = c[getClassNameSymbol](); // "C"
bigint
类型是即将在 ES2020 中发布的一种新的原始值类型,主要用于大数运算,目前该提议已经到了 stage-4(定稿)阶段,点此可查看提议详情。bigint
类型有几个特点:
7n/4n === 1n
,7n/3/ === 2n
bigint
类型进行运算,与 number
类型混合运算时会报 TypeError
,因为混合运算可能会导致精度丢失bigint
和 number
类型 可以 进行比较,比如 7n > 4 === true
,因为比较不会导致精度丢失bigint
和 number
类型 不是严格相等 的,但是是 宽松相等 的,比如 1n !== 1
但是 2n ==2
bigint
也可以和 number
一样,可以作为 if
等条件语句的条件,其隐式转换和 number
类似bigint
类型和 number
类型非常相似,比如都可以通过字面量来定义,在数字字面量后面加一个 n
即可定义个 bigint
的字面量:
let count = 123n;
你也可以使用 BigInt
函数来创建 bigint
字面量:
let num1 = BigInt(123); // 可以使用 number 类型的数字
let num2 = BigInt('123'); // 可以使用字符串
let num3 = BigInt(0x1fff); // 可以使用十六进制
let num4 = BigInt(0b1fff); // 也可以使用二进制
BigInt
构造函数有两个静态方法,分别是 BigInt.asUintN(width, bigint)
和 BigInt.asIntN(width, bigint)
,这两个函数的作用是将一个 bigint
转换为固定位数的 bitint
数值(有符号或者无符号)。BigInt.asUintN(width, bigint)
将 bigint
转换为 \-2n \*\* (width - 1n)
到 2n \*\* (width - 1n) - 1n
范围内的数字,超过此范围的话会导致溢出。BigInt.asIntN(width, bigint)
则会将 bigint
转换为 0
到 2n \*\* width - 1n
范围内的数字,超过此范围的话会导致溢出。
除了原始值类型外,TypeScript 中还有别的类型,包括:
object
any
void
never
object
类型指的是除原始值类型以外的类型,也就是除 number
, boolean
, string
, null
, undefined
, symbol
以及 bigint
以外的所有类型。
元组可以让你用类似于数组的形式来表示一组类型,在元组中的每一个元素的类型都是 确定 的,且 可以是不同 的(这点和数组不一样,数组中所有元素的类型 都是一样 的),比如你可能想将一个字符串和数组放在一个数组中来表示一对值,这种类型就可以用元组来实现:
let pair: [string, number] = ['foo', 1];
pair = [1, 1]; // Error: Type 'number' is not assignable to type 'string'.
console.log(pair[0].slice(0)); // OK
console.log(pair[1].slice(0)); // Error: Property 'slice' does not exist on type 'number'.
console.log(pair[2]); // Error: Tuple type '[string, number]' of length '2' has no element at index '3'.
枚举类型是用于组织一组 确定的 、 标准化的 数据类型的一种类型,它是 TypeScript 中对于 JavaScript 的类型补充。枚举类型来自于 C#,它可以为一组数值提供更加友好的名字:
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
默认情况下,枚举的值从 0
开始,当然你可以为每一个枚举成员设置自定义的枚举值,比如,我们可以让枚举值从 1
开始,而不是从 0
开始:
enum Color {
Red = 1,
Green,
Blue,
}
let c: Color = Color.Green;
甚至,你可以为每个枚举成员都自定义枚举值:
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
let c: Color = Color.Green;
枚举有一个非常有用的特性就是,你可以通过枚举值获取枚举成员的名字,比如,现在有一个枚举值是 2
,但是我并不知道它所对应的颜色是什么,这时候你就可以用到这个特性了:
enum Color {
Red = 1,
Green,
Blue,
}
let colorName: string = Color[2];
console.log(colorName); // Displays 'Green' as its value is 2 above
上面的例子中枚举的值都是常数项,而枚举实际上还可以使用计算所得项,计算所得项的意思就是可以在运行时计算枚举的值,比如:
enum Color {
Red,
Blue = 'Blue'.length,
}
注意,在使用计算所得项时,计算所得项前面的枚举可以不需要手动赋值,比如上面这个例子中的 Red
,但是任意一个计算所得项后面的所有枚举,都需要手动赋值,否则会报错:
enum Color {
Red,
Blue = 'Blue'.length,
Green // error TS1061: Enum member must have initializer.
常数项是可以在 TypeScript 编译阶段求值的值,除了常数项以外的值都是计算所得项,常数项仅包含以下几种情况:
+
, \-
, ~
一元运算符应用于常数表达式+
, \-
, \*
, /
, %
, <<
, \>>
, \>>>
, &
, |
, ^
二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错除以上情况外均为计算所得项。
很多时候我们并不能确定一个值的类型,比如某个值来自于用户输入,或者来自第三方库。这时候你也许想选择不使用类型检查,你可以使用 any
类型来实现:
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
any
类型是将 JavaScript 迁移至 TypeScript 时最常使用的类型,你可以任意控制对某个值使用或者不使用类型检查。你可能会觉得 Object
也有类似的作用(像其他语言一样),但是在 TypeScript 中,Object
有点不一样,你可以给 Object
类型赋任何值,但是不能在 Object
类型上调用任何方法:
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
// Property 'toFixed' does not exist on type 'Object'.
当然你可能不想让某个值完全拥有任意类型,你可能只是想类型的一部分是任意类型,你可以和其他类型组合起来使用 any
,比如:
let list: any[] = [1, true, "free"];
list[1] = 100;
void
有一点像 any
的反面:表示任何值的缺失。void
最常见的地方就是函数的返回值,表示该函数没有任何返回值:
function warnUser(): void {
console.log("This is my warning message");
}
将一个值声明为 void
类型没什么用,因为你只能给它赋值 null
(如果 \--strictNullChecks
没有指定的话)或者 undefined
。
never
类型表示一个值 永远 不会出现。比如一个抛出错误的函数的返回值类型就是 never
,因为这个函数的返回值 永远 不会出现。never
是任何类型的子类,因此可以赋值给任何类型,而 never
不能赋值给除 never
自己以外的任何类型,就算是 any
也不能赋值给 never
。一些 never
的例子:
// Function returning never must have unreachable end point
function error(message: string): never {
throw new Error(message);
}
// Inferred return type is never
function fail() {
return error("Something failed");
}
// Function returning never must have unreachable end point
function infiniteLoop(): never {
while (true) {}
}
有时候你可能比 TypeScript 编译器更清楚一个值的类型。这时候你可以使用类型断言来告诉编译器:“相信我,我比你更清楚这个值的类型,我知道我在做什么”。类型断言有点类似于别的语言的强制转换,但是类型断言不会对值做任何操作,只是检查类型而已。类型断言假设 你(编程者) 已经做了必要的类型检查了。类型断言有两种方式,第一种是尖括号形式:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
或者你可以使用 as
形式:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
这两种形式没有区别,但是当你在使用 JSX 的时候,只有 as
是合法的(因为无法区分一个尖括号是 JSX 还是类型断言)。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8