https://www.typescriptlang.org/docs/handbook/basic-types.html 官网
https://ts.xcatliu.com/basics/primitive-data-types.html 入门网站
原始数据类型
JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 [Symbol](http://es6.ruanyifeng.com/#docs/symbol)。
本节主要介绍前五种原始数据类型在 TypeScript 中的应用。
let isDone: boolean = false; // 编译通过// 编译未通过 因为返回的是Boolean类型而非boolean类型let createdByNewBoolean: boolean = new Boolean(1);// index.ts(1,4): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.// ES6 中的二进制表示法let binaryLiteral: number = 0b1010;// ES6 中的八进制表示法let octalLiteral: number = 0o744;let notANumber: number = NaN;let infinityNumber: number = Infinity;// 其中 0b1010 和 0o744 是 ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。// 其他原始类型的用法与boolean一致
空值void, undefined, null
function alertName(): void { // 表示函数无返回值alert('My name is Tom');}// 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:let unusable: void = undefined;let u: undefined = undefined;let n: null = null;// undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null。// 与 void 的区别是,undefined 和 null 是所有类型的子类型。// 也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:// 这样不会报错let num: number = undefined;// 这样也不会报错let u: undefined;let num: number = u;// 而 void 类型的变量不能赋值给 number 类型的变量:let u: void;let num: number = u;// index.ts(2,5): error TS2322: Type 'void' is not assignable to type 'number'.
任意值
任意值(Any)用来表示允许赋值为任意类型。
let myFavoriteNumber: string = 'seven';myFavoriteNumber = 7;// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.let myFavoriteNumber: any = 'seven';myFavoriteNumber = 7;
任意值的属性和方法
在任意值上访问任何属性都是允许的:
let anyThing: any = 'hello';console.log(anyThing.myName);console.log(anyThing.myName.firstName);
也允许调用任何方法:
let anyThing: any = 'Tom';anyThing.setName('Jerry');anyThing.setName('Jerry').sayHello();anyThing.myName.setFirstName('Cat');
可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。变量如果在声明的时候,未指定其类型与值,那么它会被识别为任意值类型.
let something;something = 'seven';something = 7;something.setName('Tom');
类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
以下代码虽然没有指定类型,但是会在编译的时候报错:
let myFavoriteNumber = 'seven';myFavoriteNumber = 7;// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.// 事实上,它等价于:let myFavoriteNumber: string = 'seven';myFavoriteNumber = 7;
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 **any** 类型而完全不被类型检查:
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number;myFavoriteNumber = 'seven';myFavoriteNumber = 7;let myFavoriteNumber: string | number;myFavoriteNumber = true;// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.// Type 'boolean' is not assignable to type 'number'.
访问联合类型的属性或方法
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {return something.length;}// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.// length 不是 string 和 number 的共有属性,所以会报错。function getString(something: string | number): string {return something.toString();}let myFavoriteNumber: string | number;myFavoriteNumber = 'seven';console.log(myFavoriteNumber.length); // 5myFavoriteNumber = 7;console.log(myFavoriteNumber.length); // 编译时报错// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
接口
接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
interface Person {name: string;age: number;}let tom: Person = {name: 'Tom',age: 25};let tom1: Person = { // 少一些属性是不允许的name: 'Tom'};let tom2: Person = { // 多一些属性是不允许的name: 'Tom',age: 25,gender: 'male'};// 可以定义可选属性interface Person {name: string;age?: number;}let tom: Person = { // 可选属性的含义是该属性可以不存在。name: 'Tom'};// 可以定义任意属性interface Person {name: string;age?: number;[propName: string]: any;}let tom: Person = {name: 'Tom',gender: 'male'};// 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:interface Person {name: string;age?: number;[propName: string]: string;}let tom: Person = {name: 'Tom',age: 25,gender: 'male'};// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.// Index signatures are incompatible.// Type 'string | number' is not assignable to type 'string'.// Type 'number' is not assignable to type 'string'.
只读属性的定义
interface Person {readonly id: number;name: string;age?: number;[propName: string]: any;}let tom: Person = {id: 89757,name: 'Tom',gender: 'male'};tom.id = 9527;// index.ts(14,5): error TS2540: Cannot assign to 'id'// because it is a constant or a read-only property.
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
interface Person {readonly id: number;name: string;age?: number;[propName: string]: any;}let tom: Person = {name: 'Tom',gender: 'male'};tom.id = 89757;// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.// Property 'id' is missing in type '{ name: string; gender: string; }'.// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,报错信息有两处,第一处是在对 tom 进行赋值的时候,没有给 id 赋值。
第二处是在给 tom.id 赋值的时候,由于它是只读属性,所以报错了。
function的定义
interface SearchFunc {(source: string, subString: string): boolean;}interface SearchObj {mySearch: (source: string, subString: string) => boolean;}const obj: SearchObj = {mySearch: (str, subString) => {}}let mySearch: SearchFunc;mySearch = function(source: string, subString: string) {let result = source.search(subString);return result > -1;}
可索引类型的定义
interface StringArray {[index: number]: string;}interface StringArrayObje {myArray: StringArray[];}let myArray: StringArray;myArray = ["Bob", "Fred"];let myStr: string = myArray[0];interface NumberDictionary {[index: string]: number;length: number; // ok, length is a numbername: string; // error, the type of 'name' is not a subtype of the indexer}
class 接口的定义
interface ClockInterface {currentTime: Date;}class Clock implements ClockInterface {currentTime: Date;constructor(h: number, m: number) { }}interface ClockInterface {currentTime: Date;setTime(d: Date);}class Clock implements ClockInterface {currentTime: Date;setTime(d: Date) {this.currentTime = d;}constructor(h: number, m: number) { }}
类的静态和实例方面的区别
使用类和接口时,有必要记住,类有两种类型:静态类型和实例端类型。您可能会注意到,如果使用构造签名创建接口并尝试创建实现此接口的类,则会收到错误:
interface ClockConstructor {new (hour: number, minute: number);}class Clock implements ClockConstructor {currentTime: Date;constructor(h: number, m: number) { }}
这是因为当类实现接口时,只检查类的实例端。由于构造函数位于静态方面,因此它不包含在此检查中。
相反,您需要直接使用类的静态方面。在这个例子中,我们ClockConstructor为构造函数和ClockInterface实例方法定义了两个接口。然后为方便起见,我们定义了一个构造函数createClock,该函数创建传递给它的类型的实例。
interface ClockConstructor {new (hour: number, minute: number): ClockInterface;}interface ClockInterface {tick();}function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {return new ctor(hour, minute);}class DigitalClock implements ClockInterface {constructor(h: number, m: number) { }tick() {console.log("beep beep");}}class AnalogClock implements ClockInterface {constructor(h: number, m: number) { }tick() {console.log("tick tock");}}let digital = createClock(DigitalClock, 12, 17);let analog = createClock(AnalogClock, 7, 32);
因为createClock第一个参数是type ClockConstructor,in createClock(AnalogClock, 7, 32),它检查AnalogClock具有正确的构造函数签名。
接口的扩展,接口可以继承接口
// 与类一样,接口可以相互扩展。这允许您将一个接口的成员复制到另一个接口,// 这使您可以更灵活地将接口分成可重用的组件。interface Shape {color: string;}interface Square extends Shape {sideLength: number;}let square = <Square>{};square.color = "blue";square.sideLength = 10;// 接口可以扩展多个接口,创建所有接口的组合。interface Shape {color: string;}interface PenStroke {penWidth: number;}interface Square extends Shape, PenStroke {sideLength: number;}let square = <Square>{};square.color = "blue";square.sideLength = 10;square.penWidth = 5.0;
混合类型
正如我们前面提到的,接口可以描述现实世界JavaScript中存在的丰富类型。由于JavaScript的动态性和灵活性,您可能偶尔会遇到一个对象,该对象可以作为上述某些类型的组合。
一个这样的例子是一个充当函数和对象的对象,具有其他属性:
interface Counter {(start: number): string;interval: number;reset(): void;}function getCounter(): Counter {let counter = <Counter>function (start: number) { };counter.interval = 123;counter.reset = function () { };return counter;}let c = getCounter();c(10);c.reset();c.interval = 5.0;
接口扩展类
当接口类型扩展类类型时,它继承类的成员但不继承它们的实现。就好像接口已经声明了类的所有成员而没有提供实现。接口甚至继承基类的私有成员和受保护成员。这意味着当您创建一个扩展具有私有或受保护成员的类的接口时,该接口类型只能由该类或其子类实现。
当您具有大型继承层次结构但希望指定您的代码仅使用具有某些属性的子类时,这非常有用。除了继承基类之外,子类不必相关。例如:
class Control {private state: any;}interface SelectableControl extends Control {select(): void;}class Button extends Control implements SelectableControl {select() { }}class TextBox extends Control {select() { }}// Error: Property 'state' is missing in type 'Image'.class Image implements SelectableControl {select() { }}class Location {}
在上面的示例中,SelectableControl包含所有成员Control,包括私有state属性。由于state是私人成员,因此只能Control实施后代SelectableControl。这是因为只有后代Control会有一个state私有成员来源于同一个声明,这是私有成员兼容的要求。
在Control类中,可以state通过实例访问私有成员SelectableControl。有效地,一个SelectableControl像作用Control,是已知的具有select方法。在Button与TextBox类的亚型SelectableControl(因为它们都继承Control并有select方法),但Image和Location类都没有。
类型兼容
interface Named {name: string;}class Person {name: string;}let p: Named;// OK, because of structural typingp = new Person();
interface Named {name: string;}let x: Named;// y's inferred type is { name: string; location: string; }let y = { name: "Alice", location: "Seattle" };x = y; // okfunction greet(n: Named) {console.log("Hello, " + n.name);}greet(y); // OKlet x = (a: number) => 0;let y = (b: number, s: string) => 0;y = x; // OKx = y; // Errorlet items = [1, 2, 3];// Don't force these extra parametersitems.forEach((item, index, array) => console.log(item));// Should be OK!items.forEach(item => console.log(item));let x = () => ({name: "Alice"});let y = () => ({name: "Alice", location: "Seattle"});x = y; // OKy = x; // Error, because x() lacks a location property
枚举兼容
枚举与数字兼容,数字与枚举兼容。来自不同枚举类型的枚举值被认为是不兼容的
enum Status { Ready, Waiting };enum Color { Red, Blue, Green };let status = Status.Ready;status = Color.Green; // Error
类的兼容
注意,类中的私有成员和受保护成员会影响其兼容性
class Animal {feet: number;constructor(name: string, numFeet: number) { }}class Size {feet: number;constructor(numFeet: number) { }}let a: Animal;let s: Size;a = s; // OKs = a; // OK
子类型与分配
到目前为止,我们使用的是“兼容”,这不是语言规范中定义的术语。在TypeScript中,有两种兼容性:子类型和赋值。它们的不同之处仅在于赋值扩展了与规则的子类型兼容性,以允许与相应的数值进行分配,来往any和来自enum相应的数值。
语言中的不同位置使用两种兼容机制中的一种,具体取决于具体情况。出于实际目的,类型兼容性由赋值兼容性决定,即使在implements和extends子句的情况下也是如此。
数组
let fibonacci: number[] = [1, 1, 2, 3, 5];let fibonacci: number[] = [1, '1', 2, 3, 5];// index.ts(1,5): error TS2322: Type '(number | string)[]' is not assignable to type 'number[]'.// Type 'number | string' is not assignable to type 'number'.// Type 'string' is not assignable to type 'number'.let fibonacci: number[] = [1, 1, 2, 3, 5];fibonacci.push('8');// index.ts(2,16): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
数组泛型
也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
用接口表示数组
接口也可以用来描述数组:
interface NumberArray {[index: number]: number;}let fibonacci: NumberArray = [1, 1, 2, 3, 5];
NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number。
any 在数组中的应用
一个比较常见的做法是,用 any 表示数组中允许出现任意类型:
let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
类数组
类数组(Array-like Object)不是数组类型,比如 arguments:
function sum() {let args: number[] = arguments;}// index.ts(2,7): error TS2322: Type 'IArguments' is not assignable to type 'number[]'.// Property 'push' is missing in type 'IArguments'.
事实上常见的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
function sum() {let args: IArguments = arguments;}
关于内置对象,可以参考内置对象一章。
函数
函数是 JavaScript 中的一等公民
在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):
函数声明
// 函数声明(Function Declaration)function sum(x, y) {return x + y;}// 函数表达式(Function Expression)let mySum = function (x, y) {return x + y;};
一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:
function sum(x: number, y: number): number {return x + y;}
注意,输入多余的(或者少于要求的)参数,是不被允许的:
function sum(x: number, y: number): number {return x + y;}sum(1, 2, 3);// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.sum(1);// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
函数表达式
如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样:
let mySum = function (x: number, y: number): number {return x + y;};
这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {return x + y;};
注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。
在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
在 ES6 中,=> 叫做箭头函数,应用十分广泛,可以参考 ES6 中的箭头函数。
用接口定义函数的形状
我们也可以使用接口的方式来定义一个函数需要符合的形状:
interface SearchFunc {(source: string, subString: string): boolean;}let mySearch: SearchFunc;mySearch = function(source: string, subString: string) {return source.search(subString) !== -1;}
可选参数
前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?
与接口中的可选属性类似,我们用 ? 表示可选的参数:
function buildName(firstName: string, lastName?: string) {if (lastName) {return firstName + ' ' + lastName;} else {return firstName;}}let tomcat = buildName('Tom', 'Cat');let tom = buildName('Tom');
需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了:
function buildName(firstName?: string, lastName: string) {if (firstName) {return firstName + ' ' + lastName;} else {return lastName;}}let tomcat = buildName('Tom', 'Cat');let tom = buildName(undefined, 'Tom');// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
参数默认值
在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数:
function buildName(firstName: string, lastName: string = 'Cat') {return firstName + ' ' + lastName;}let tomcat = buildName('Tom', 'Cat');let tom = buildName('Tom');
此时就不受「可选参数必须接在必需参数后面」的限制了:
function buildName(firstName: string = 'Tom', lastName: string) {return firstName + ' ' + lastName;}let tomcat = buildName('Tom', 'Cat');let cat = buildName(undefined, 'Cat');
关于默认参数,可以参考 ES6 中函数参数的默认值。
剩余参数
ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数):
function push(array, ...items) {items.forEach(function(item) {array.push(item);});}let a = [];push(a, 1, 2, 3);
事实上,items 是一个数组。所以我们可以用数组的类型来定义它:
function push(array: any[], ...items: any[]) {items.forEach(function(item) {array.push(item);});}let a = [];push(a, 1, 2, 3);
注意,rest 参数只能是最后一个参数,关于 rest 参数,可以参考 ES6 中的 rest 参数。
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
利用联合类型,我们可以这么实现:
function reverse(x: number | string): number | string {if (typeof x === 'number') {return Number(x.toString().split('').reverse().join(''));} else if (typeof x === 'string') {return x.split('').reverse().join('');}}
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。
这时,我们可以使用重载定义多个 reverse 的函数类型:
function reverse(x: number): number;function reverse(x: string): string;function reverse(x: number | string): number | string {if (typeof x === 'number') {return Number(x.toString().split('').reverse().join(''));} else if (typeof x === 'string') {return x.split('').reverse().join('');}}
上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
this的使用
错误的情况
let deck = {suits: ["hearts", "spades", "clubs", "diamonds"],cards: Array(52),createCardPicker: function() {return function() {let pickedCard = Math.floor(Math.random() * 52);let pickedSuit = Math.floor(pickedCard / 13);return {suit: this.suits[pickedSuit], card: pickedCard % 13};}}}let cardPicker = deck.createCardPicker();let pickedCard = cardPicker();alert("card: " + pickedCard.card + " of " + pickedCard.suit);
有警告的情况
类型this.suits[pickedSuit]仍然存在any
let deck = {suits: ["hearts", "spades", "clubs", "diamonds"],cards: Array(52),createCardPicker: function() {// NOTE: the line below is now an arrow function, allowing us to capture 'this' right herereturn () => {let pickedCard = Math.floor(Math.random() * 52);let pickedSuit = Math.floor(pickedCard / 13);return {suit: this.suits[pickedSuit], card: pickedCard % 13};}}}let cardPicker = deck.createCardPicker();let pickedCard = cardPicker();alert("card: " + pickedCard.card + " of " + pickedCard.suit);
正确的使用
interface Card {suit: string;card: number;}interface Deck {suits: string[];cards: number[];createCardPicker(this: Deck): () => Card;}let deck: Deck = {suits: ["hearts", "spades", "clubs", "diamonds"],cards: Array(52),// NOTE: The function now explicitly specifies that its callee must be of type DeckcreateCardPicker: function(this: Deck) {return () => {let pickedCard = Math.floor(Math.random() * 52);let pickedSuit = Math.floor(pickedCard / 13);return {suit: this.suits[pickedSuit], card: pickedCard % 13};}}}let cardPicker = deck.createCardPicker();let pickedCard = cardPicker();alert("card: " + pickedCard.card + " of " + pickedCard.suit);
类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。
语法
<类型>值
或
值 as 类型
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。
例子:将一个联合类型的变量指定为一个更加具体的类型
之前提到过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {return something.length;}// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.
而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型的属性或方法,比如:
function getLength(something: string | number): number {if (something.length) {return something.length;} else {return something.toString().length;}}// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.// index.ts(3,26): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.
上例中,获取 something.length 的时候会报错。
此时可以使用类型断言,将 something 断言成 string:
function getLength(something: string | number): number {if ((<string>something).length) {return (<string>something).length;} else {return something.toString().length;}}function getLength(something: string | number): number {if (something as string).length) {return (<string>something).length;} else {return something.toString().length;}}
类型断言的用法如上,在需要断言的变量前加上 <Type> 即可。
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的:
function toBoolean(something: string | number): boolean {return <boolean>something;}// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.// Type 'number' is not comparable to type 'boolean'.
interface SquareConfig {color?: string;width?: number;}function createSquare(config: SquareConfig): { color: string; area: number } {// ...}// 错误,因为传参数不正确,colourlet mySquare = createSquare({ colour: "red", width: 100 });let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);// 绕过检查的方法let squareOptions = { colour: "red", width: 100 };let mySquare = createSquare(squareOptions);
never类型
一个函数连void都不返回的情况下,使用 never类型
// Function returning never must have unreachable end pointfunction error(message: string): never {throw new Error(message);}// Inferred return type is neverfunction fail() {return error("Something failed");}// Function returning never must have unreachable end pointfunction infiniteLoop(): never {while (true) {}}
Object 类型
object是代表非原始型,即没有任何东西一个类型number,string,boolean,symbol,null,或undefined。
使用object类型,Object.create可以更好地表示API 。例如:
declare function create(o: object | null): void;create({ prop: 0 }); // OKcreate(null); // OKcreate(42); // Errorcreate("string"); // Errorcreate(false); // Errorcreate(undefined); // Error
