[关闭]
@yacent 2017-12-04T00:05:35.000000Z 字数 14046 阅读 1297

TypeScript学习笔记

TypeScript


网址: ts.xcatliu.com


简介

什么是TypeScript

TypeScript是JavaScript的一个超集,主要提供了 类型系统对ES6的支持

TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。

TypeScript的好处

但是TypeScript也有自己的缺点

Hello TypeScript

简单的例子

  1. function sayHello(person: string) {
  2. return 'Hello' + person;
  3. }
  4. let user = 'Tom';
  5. console.log(sayHello(user));

然后执行
tsc hello.ts

这时候会生成一个编译好的 hello.js

  1. function sayHello(person) {
  2. return 'Hello' + person;
  3. }
  4. var user = 'Tom';
  5. console.log(sayHello(user));

TypeScript中,使用 : 指定变量的类型;
如果上面的代码当中,user的类型不是 string,ts会报错,但是还是会编译出一个 hello.js

TypeScript只会进行静态检查, 如果发现有错误,编译的时候就会报错。

如果想要在报错的时候就终止js文件的生成,可以在 tsconfig.json 当中配置 noEmitOnError


基础

原始数据类型

在 JavaScript当中,有五种基本数据类型
booleannumberstringundefinednull 以及 ES6的 Symbol

布尔值

  1. let isDone: boolean = false

注意: new Boolean(1) 其返回的类型不是 boolean,而是 object,如果是直接调用 Boolean(1),那么其返回的就是 boolean类型

在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样,不再赘述。

数值

使用 number 定义数值类型

  1. let decLiteral: number = 6;
  2. let hexLiteral: number = 0xf00d;

字符串

使用 string 定义字符串类型:

  1. let myName: string = 'Tom'

空值

JavaScript没有空值(void) 的概念,在TypeScript中,可以用 void 表示没有任何返回值的函数

  1. function alertName(): void {
  2. console.log(111);
  3. }

变量声明为 void 并没有什么用,只能将它赋值为 undefined 或者 null

Null和Undefined

在TypeScript中,可以使用 nullundefined 来定义这两个原始数据类型

  1. let u: undefined = undefined;
  2. let n: null = null;

undefined 类型的变量只能被赋值为 undefinednull 类型的变亮只能被赋值为 null

void 区别在于,undefinednull 是所有类型的子类型,即 undefined 类型的变量,可以赋值给 number 类型的变量

  1. let num: number = undefined;
  2. // 通过
  3. let u: void;
  4. let num1: number = u;
  5. // 编译错误

任意值

任意值(Any)用来表示允许赋值为任意类型

相比于普通类型,普通类型在赋值的过程中改变类型是不允许的

  1. let m: string = 'seven';
  2. m = 7;
  3. // index.ts(2, 1): error TS2322: Type 'number' is not assignable to type 'string'

但如果是 any 类型,则允许赋值的时候改变类型

  1. let m: any = 'seven';
  2. m = 7;

任意值:

可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

在ts当中,如果变亮在声明的时候,未指定其类型,那么它会被识别为任意类型(any),与原来的 js 一样。

  1. let s; // 注意这里并没有给他赋值,所以会被推断为 any类型
  2. s = 'seven';
  3. s = 7;

类型推论

如果没有明确的指定类型,那么TypeScript会依照类型推论(Type Inference)的规则推断出一个类型。

  1. let m = 'seven';
  2. m = 7;
  3. // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

实际上等价于

  1. let m: string = 'seven';
  2. m = 7;
  3. // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

TypeScript会在声明并且赋值的情况下,如果没有迷宫却的指定类型,则会推测出一个类型,这就是类型推论

如果定义的时候没有赋值,则会被推断为 any 类型


联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种

  1. let m: string | number;
  2. m = 'seven';
  3. m = 7;
  4. m = true;
  5. // Type 'boolean' is not assignable to type 'string | number'

联合类型使用 | 分割每个类型

访问联合类型的属性或方法

当TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问共有的属性或方法

  1. function getLength(s: string | number):number {
  2. return s.length
  3. }
  4. // error TS2339: Property 'length' does not exist on type 'string | number'.

因为 length 不是 string 和 number 的共同属性,所以会报错。


对象的类型——接口

在TypeScript中,我们使用接口(Interfaces)来定义对象的类型

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。(即描述对象的格式)

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. let tom: Person = {
  6. name: 'Tom',
  7. age: 25
  8. }

接口字母一般首字母大写,并且,赋值的时候,变量的形状必须和接口的形状保持一致

可选属性
有时候,我们希望不要完全匹配一个形状,那么可以使用可选属性。但是,这是仍然不允许添加未定义的属性。

  1. interface Person {
  2. name: string;
  3. age?: number;
  4. }
  5. let tom: Person = {
  6. name: 'Tom'
  7. };

任意属性
即允许接口当中,可以任意添加未曾定义过的属性

  1. interface Person {
  2. name: string;
  3. age?: number;
  4. [propName: string]: any;
  5. }
  6. let tom: Person = {
  7. name: 'Tom',
  8. gender: 'male'
  9. }

使用 [propName: string] 定义了任意属性取 string 类型的值

tips: 一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性

只读属性
有时候希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly定义只读属性

  1. interface Person {
  2. readonly id number;
  3. name: string;
  4. }
  5. let tom: Person = {
  6. id: 89757,
  7. name: 'Tom
  8. }
  9. tom.id = 123;
  10. // error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

数组的类型

在TypeScript中,数组类型有多种定义方式,比较灵活

[类型 + 方括号] 表示法 最常用

其中,类型可以是之前提到过的所有数据类型,string、number、boolean、any、interface、联合类型等

  1. let fibo: number[] = [1, 1, 2, 3, 5]
  2. let union: (number | string)[] = [1, '1']

数组中的项 不允许 出现其他的类型

数组泛型
也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组

let fibo: Array<number> = [1, 1, 2, 3, 5]

接口表示数组

  1. interface NumberArray {
  2. [index: number]: number;
  3. }
  4. let fibo: NumberArray = [1, 1, 2, 3, 5];

NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number。

类数组

类数组(Array-like Object)不是数组类型,即伪数组,不是数组类型,实际上,类数组有自己的接口定义,如 IArguments NodeList HTMLCollection

  1. function sum() {
  2. let args: IArguments = arguments;
  3. }

函数的类型

函数是有输入和输出的,需要同时考虑到输入和输出的类型

函数声明的类型定义

  1. function sum(x: number, y: number): number {
  2. return x + y;
  3. }

输入过多或者过少的参数都是不被允许的,一般情况下,其实比较常用的是限制函数的参数的数据类型,而对于其返回的数据类型,通常是不会做限制的。

函数表达式的类型定义 不常用

  1. let mySum: (x: number, y: number) => number = (x: number, y: number):number => {
  2. return x + y;
  3. }

注意两个点:

需要掌握的其他知识点

可选参数

通接口类型当中的一样,可以设定 可选参数,但是需要注意,可选参数必须放在必选参数的后面

  1. function mySum(x: number, y?: number): number {
  2. // ...
  3. }
参数默认值

ES6当中,允许我们在参数当中传入默认值


类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型

<类型>值值 as 类型
tsx语法当中,必须使用后一种

类型断言的使用之处,比如在联合类型当中,使用属性或者方法的时候,必须使用联合类型当中共有的属性或方法

  1. function getLength(m: string | number) {
  2. if (m.length) {
  3. return m.length;
  4. }
  5. // ...
  6. }
  7. // 报错

因为 number没有 length 属性,但我们可以使用类型断言,将m 断言成 string

  1. function getLength(m: string | number) {
  2. if ((<string>m).length) {
  3. return (<string>m).length;
  4. }
  5. }
  6. // tsx 下必须写成 as 的形式
  7. function getLength(m: string | number) {
  8. if ((m as string).length) {
  9. return (m as string).length;
  10. }
  11. }

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的

当然,你也可以使用 typeofinstanceof等 来进行类型的判断

  1. function getLength(m: string | number) {
  2. if (typeof m === 'string') {
  3. return m.length;
  4. }
  5. // ...
  6. }

声明文件

有时候使用第三方库的时候,比如jq,需要声明 $ 是什么鬼东西

使用 declare 关键字

  1. declare var $: (string) => any;
  2. $('foo')

declare 定义的类型只会用于编译时进行检查,在编译结果中会被删除。


内置对象

详情请看文档
通常来说,js当中有的内置对象,如 Math,Date等等,typeScript都已经是封装好了


进阶

类型别名

使用 type 创建类型别名,通过类型别名用来给一个类型起个新的名字,常用语联合类型当中,在平常的使用中,使用频率比较低。
此外,type 还是用来定义字符串字面量类型。

  1. type Name = string;
  2. type NameResolver = () => string;
  3. type NameOrResolver = Name | NameResolver;
  4. let m: NameOrResolver;

字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个

  1. type EventNames = 'click' | 'scroll' | 'mousemove';
  2. function handleEvent(ele: Element, event: Eventnames) {
  3. // do something
  4. }

元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象

  1. let xcatliu: [string, number] = ['Xcat Liu', 25]

如果是声明的时候就定义元组,那么必须把元组当中的类型都对齐,不然会报错,如果不是声明的时候定义,那么就可以一个个进行赋值

  1. let xcatliu: [string, number];
  2. xcatliu[0] = 'Xcat Liu';
  3. xcatliu[1] = 25;

越界的元素
当赋值给越界的元素时,它类型会被限制为元组中每个类型的 联合类型

  1. let xcatliu: [string, number] = ['Xcat Liu', 25, 'http://']

上面的第三项,只需要满足 string | number 类型即可


枚举

枚举使用 enum 关键字来进行定义

  1. enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}

使用 enum 实际上会进行一个映射

  1. console.log(Days["Sun"] === 0); // true

因为其编译过程的结果如下,自己加了一个双向映射

  1. var Days;
  2. (function(Days) {
  3. Days[Days["Sun"] = 0] = "Sun"
  4. })

也可以手动进行赋值

  1. enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat}

未手动赋值的枚举项,会接着上一个枚举项进行递增,递增步长为 1


类的概念

ES6中类的用法

其他和ES6都是相同的,要注意一点,是 静态方法

  1. class Animal {
  2. static isAnimal(a) {
  3. return a instanceof Animal
  4. }
  5. }
  6. let a = new Animal('Jack');
  7. Animal.isAnimal(a); // true
  8. a.isAnimal(a); // TypeError: a.isAnimal is not a function

使用 static 修饰符修饰的方法称为 静态方法,它们不需要实例化,而是通过类来调用

ES7中的类的用法

实例属性
ES6中实例的属性只能通过构造函数中的 this.xxx 来定义,而ES7当中,可以直接在类里面定义

  1. class Animal {
  2. name: 'Jack';
  3. constructor() {
  4. // ...
  5. }
  6. }

TypeScript中类的用法

修饰符

TypeScript可以使用三种访问修饰符(Access Modifiers),分别是 public privateprotect

抽象类

关键字 abstract 用于定义抽象类和其中的抽象方法

  1. abstract class Animal {
  2. public name;
  3. public constructor(name) {
  4. this.name = name;
  5. }
  6. public abstract sayHi();
  7. }
  8. let a = new Animal('Jack'); // error TS2511: Cannot create an instance of the abstract class 'Animal'.
  9. class Cat extends Animal {
  10. public sayHi() {
  11. console.log(`Meow. My name is ${this.name}`)
  12. }
  13. }
类的类型

给类加上TypeScript的类型很简单,与接口类似

  1. class Animal {
  2. name: string,
  3. constructor(name: string) {
  4. this.name = name;
  5. }
  6. sayHi() {
  7. return `My name is ${this.name}`;
  8. }
  9. }

类与接口

之前学习过,接口(Interfaces)可以用于对[对象的形状(Shaped)]进行描述

类实现 接口

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这
个特性大大提高了面向对象的灵活性。

  1. interface Alarm {
  2. alert(); // 此处不是具体的实现,而是一个声明或者语句,具体的实现需要到类当中去实现该方法所展示的东西
  3. }
  4. class Door {}
  5. class SecurityDoor extends Door implements Alarm {
  6. alert() {
  7. // do something
  8. }
  9. }
  10. class Car implements Alarm {
  11. alert() {
  12. // do something
  13. }
  14. }

一个类可以实现多个接口

  1. interface Alarm {
  2. alert();
  3. }
  4. interface Light {
  5. lightOn();
  6. }
  7. class Car implements Alarm, Light {
  8. alert() {}
  9. lightOn() {}
  10. }

接口继承关系

  1. interface Alarm extends MoreAlart {
  2. // something
  3. }

泛型(Generics)

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

  1. function createArray(length: number, value: any): Array<any> {
  2. let result = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. createArray(3, 'x'); // ['x', 'x', 'x']

上面可以实现返回初始化值的数组,但是有一个问题,其不能规定返回的数据的类型,即传入的变量的类型最终有可能和输出的数组的类型不同。
这个时候,泛型的用处就在这,可以规定返回数组的类型

  1. function createArray<T>(length: number, value: T): Array<T> {
  2. let result = [];
  3. for (let i = 0; i < length; i++) {
  4. result[i] = value;
  5. }
  6. return result;
  7. }
  8. createArray<string>(3, 'x'); // ['x', 'x', 'x']
  9. or
  10. createArray(3, 'x'); // ['x', 'x', 'x']

在函数名后家加了 <T>,其中 T 用来只带任意输入的类型
接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来。

多个类型参数

  1. functoin swap<T, U>(tuple: [T, U]): [U, T] {
  2. return [tuple[1], tuple[0]];
  3. }
  4. swap([7, 's']); // ['s', 7]

泛型约束

我们可以对泛型进行约束,只允许这个函数传入包含某些东西的变量,因为T可以是任何类型的,所以如果不加约束而直接使用一些并不公用的属性或方法,就会报错

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. function log<T extends Lengthwise>(arg: T): T {
  5. return arg.length
  6. }

多个参数类型之间也可以互相约束

  1. function copyFields<T extends U, U>(target: T, source: U): T {
  2. for (let id in source) {
  3. target[id] = (<T>source)[id];
  4. }
  5. return target;
  6. }
  7. let x = { a: 1, b: 2, c: 3, d: 4 };
  8. copyFields(x, { b: 10, d: 20 });

使用了两个类型参数,T 继承 U,这就保证了 U上面不会出现 T 不存在的字段

泛型类

  1. class GenericNumber<T> {
  2. zeroValue: T;
  3. add (x: T, y: T) => T
  4. }
  5. let my = new GenericNumber<number>();
  6. my.zeroValue = 0;
  7. my.add = function(x, y) {return x + y;};

声明合并


去 中文网看一下 JSX、工程配置相关的东西


JSX

TypeScript具有三种JSX模式:preservereactreact-native

可以通过命令行里使用 --jsx 标记或者 tsconfig.json 里的选项来指定模式

  1. {
  2. "files": [
  3. "index.d.ts",
  4. "test/index.ts",
  5. "test/tsx.tsx",
  6. "test/cssProperties.tsx"
  7. ],
  8. "compilerOptions": {
  9. "module": "commonjs",
  10. "lib": [
  11. "es6",
  12. "dom"
  13. ],
  14. "noImplicitAny": true,
  15. "noImplicitThis": false,
  16. "strictNullChecks": true,
  17. "strictFunctionTypes": false,
  18. "baseUrl": "../",
  19. "typeRoots": [
  20. "../"
  21. ],
  22. "types": [],
  23. "noEmit": true,
  24. "forceConsistentCasingInFileNames": true,
  25. "jsx": "preserve"
  26. }
  27. }

对于类(组件),可以指定自定义组件当中的props的类型以及state当中的类型

  1. interface PropsType {
  2. children: JSX.Element
  3. name: string
  4. }
  5. inteface StateType {
  6. name: string;
  7. age: number;
  8. }
  9. class Component extends React.Component<PropsType, StateType> {
  10. render() {
  11. return (
  12. <h2>
  13. this.props.children
  14. </h2>
  15. )
  16. }
  17. }
  18. // OK
  19. <Component>
  20. <h1>Hello World</h1>
  21. </Component>
  22. // Error: children is of type JSX.Element not array of JSX.Element
  23. <Component>
  24. <h1>Hello World</h1>
  25. <h2>Hello World</h2>
  26. </Component>
  27. // Error: children is of type JSX.Element not array of JSX.Element or string.
  28. <Component>
  29. <h1>Hello</h1>
  30. World
  31. </Component>

模块

通过 export 和 import 来进行模块的管理

定义一个外部模块,通常外部模块都是通过 .d.ts文件进行声明,这时候的模块类似于 Npm当中的包模块

  1. declare module "url" {
  2. export interface Url {
  3. protocol?: string;
  4. hostname?: string;
  5. pathname?: string;
  6. }
  7. export function parse(urlStr: string, parseQuerystring?, s?): Url
  8. }
  1. /// <reference path="node.d.ts">
  2. import * as URL from "url"
  3. let myUrl = URL.parse("http://www.typescriptlang.org");

命名空间

内部模块称之为 “命名空间”,外部模块简称为 “模块”

“内部模块”现在叫做“命名空间”。 另外,任何使用module关键字来声明一个内部模块的地方都应该使用namespace关键字来替换。 这就避免了让新的使用者被相似的名称所迷惑。

  1. namespace Validation {
  2. export interface StringValidator {
  3. isAcceptable(s: string): boolean;
  4. }
  5. const lettersRegexp = /^[A-Za-z]+$/;
  6. const numberRegexp = /^[0-9]+$/;
  7. export class LettersOnlyValidator implements StringValidator {
  8. isAcceptable(s: string) {
  9. return lettersRegexp.test(s);
  10. }
  11. }
  12. export class ZipCodeValidator implements StringValidator {
  13. isAcceptable(s: string) {
  14. return s.length === 5 && numberRegexp.test(s);
  15. }
  16. }
  17. }
  18. // Some samples to try
  19. let strings = ["Hello", "98052", "101"];
  20. // Validators to use
  21. let validators: { [s: string]: Validation.StringValidator; } = {};
  22. validators["ZIP code"] = new Validation.ZipCodeValidator();
  23. validators["Letters only"] = new Validation.LettersOnlyValidator();
  24. // Show whether each string passed each validator
  25. for (let s of strings) {
  26. for (let name in validators) {
  27. console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
  28. }
  29. }

命名空间和模块

模块的取舍

项每一个JS文件对应一个模块一样,TypeScript当中的模块文件与生成的JS文件也是一一对应的。所以对于模块的声明,能合并就合并,不要生成过多的JS文件,导致加载问题。


模块解析

相对、非相对模块导入

相对导入是以 /./../ 等开头的

  1. import Entry from "./component/Entry"

而其他形式的导入,都被当作为非相对的

  1. import * as $ from "jQuery"

模块解析策略

共有两种可用的模块解析策略: NodeClassic

Classic

这种策略以前是 TypeScript默认的解析策略。现在,主要是为了向下兼容,其搜索模块的过程是 先 tstsx,然后向涟漪,由里到外

其解析过程如下,假设下面的 import 的位置都发生在文件 /root/src/A.ts

那么,对于相对模块import {b} from './moduleB'其过程如下

1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx

对于 非相对模块import {b} from 'moduleB',其过程如下

1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/moduleB.ts
4. /root/moduleB.tsx
5. /moduleB.ts
6. /moduleB.tsx
Node

这个解析策略试图在运行时模仿 Node.js 模块解析机制,即 先 js 然后 再 package.json,最后才是 index.js

Node.js如何解析模块

同样,如下模块引用场景都是在 文件路径为 /root/src/moduleA.js当中发生

对于 相对模块var x = require("./moduleB"),其解析过程如下

1. 将 /root/src/moduleB.js 视为文件,检查其是否存在
2. 将 /root/src/moduleB 视为目录,检查是否它包含 package.json文件,并且指定了一个 main模块,在例子中,如果发现了 /root/src/moduleB/package.json 包含了 { "main": "lib/mainModule.js" },那么Node.js 会引用 /root/src/moudleB/lib/mainModule.js
3. 将 /root/src/moduleB 视为目录,如果没有 package.json文件,则检查它是否包含 index.js 文件。这个文件会被隐藏式地当作那个文件下的 main 模块
4. 如果还找不到,就会报错了

在业务代码中,可以看 引入 allActions 的过程,就很好的解释了 相对模块的解析过程

但是,非相对模块 的解析是个完全不同的过程。Node会在一个特殊的文件夹 node_modules 里查找你的模块,node_modules 可能与当前的文件在同一目录下,或者上层目录里,Node会向上级目录遍历,查找每个 node_modules 直到它找到要加载的模块

同样,如果 var x = require("moduleB"),Node会以下面的顺序去解析 moduleB,直到(即会一直匹配)有一个匹配上,其实与 相对模块 的过程差不多,只是在 node_module 下进行

1. /root/src/node_module/moduleB.js
2. /root/src/node_module/moduleB/package.json
3. /root/src/node_module/moduleB/index.js

4. /root/node_module/moduleB.js
5. /root/node_module/moduleB/package.json
6. /root/node_module/moduleB/index.js

7. /node_module/moduleB.js
8. /node_module/moduleB/package.json
9. /node_module/moduleB/index.js

TypeScript 模仿 Node的模块实现机制,在此基础上,增加了 TypeScript源文件的扩展名(.ts.tsx.d.ts),同时,TypeScript在 package.json里使用字段 types 来表示类似 main 的意义

如果,有一个导入语句 import {b} from './moduleB',在 /root/src/moduleA.ts 里,会以下面的流程来定位 "./moduleB"

1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/src/moduleB.d.ts
4. /root/src/moduleB/package.json
5. /root/src/moduleB/index.ts
6. /root/src/moduleB/index.tsx
7. /root/src/moduleB.index.d.ts

修饰器 Decorators


混入 Mixins


三斜线指令


JavaScript文件里的类型检查


如何书写声明文件

https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react/test

去学习,还有工程配置的东西`

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注