泛型1
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
首先,我们来实现一个函数createArray
function createArray(length: number, value: any): Array<any> { let result = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, 'x'); // ['x', 'x', 'x']
这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型
Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型
这时候,泛型就派上用场了
function createArray<T>(length: number, value: T): Array<T> { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray<string>(3, 'x'); // ['x', 'x','x']
泛型2
多个类型参数
function swap<T, U>(v1:T,v2:U) { console.log(v1,v2) } swap(10,20);
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
function loggingLength<T>(arg: T){ // Property 'length' does not exist on type 'T' console.log(arg.length); }
这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束
interface Length { length: number; } function loggingIdentity<T extends Length> (arg: T){ console.log(arg.length); } loggingIdentity("Hello")
声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型
函数的合并
我们可以使用重载定义多个函数类型
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(''); } }
接口的合并
接口中的属性在合并时会简单的合并到一个接口中
interface Alarm { price: number; } interface Alarm { weight: number; }
相当于:
interface Alarm { price: number; weight: number; }
注意,合并的属性的类型必须是唯一的:
interface Alarm { price: number; } interface Alarm { price: number; // 虽然重复了,但是类型都是 `number`,所以不会报错 weight: number; }
interface Alarm { price: number; } interface Alarm { price: string; // 类型不一致,会报错 weight: number; }
命名空间
在真实的应用场景中,当在一个文件中代码量过多,不容易阅读和维护的时候,我们可以通过命名空间的方式将一个文件分离为多个文件
我们来观察下面这个例子:
interface Animal{ name:string } class Cat implements Animal{ name: string constructor(name:string){ this.name = name; } sayHi(){ console.log(this.name) } } class Dog implements Animal{ name: string constructor(name:string){ this.name = name } sayHello(){ console.log(this.name) } } const c = new Cat("猫") c.sayHi() const d = new Dog("狗") d.sayHello()
当应用变得越来越大时,我们需要将代码分离到不同的文件中以便于维护
// Animal.ts namespace AnimalInfo{ export interface Animal{ name:string } }
// Cat.ts namespace AnimalInfo{ export class Cat implements Animal{ name: string constructor(name:string){ this.name = name; } sayHi(){ console.log(this.name) } } }
// Dog.ts namespace AnimalInfo{ export class Dog implements Animal{ name: string constructor(name:string){ this.name = name } sayHello(){ console.log(this.name) } } }
// index.ts const c = new AnimalInfo.Cat("猫") c.sayHi() const d = new AnimalInfo.Dog("狗") d.sayHello()
多文件编译
当涉及到多文件时,我们必须确保所有编译后的代码都被加载了。
我们有两种方式
方式一
把所有的输入文件编译为一个输出文件,需要使用 --outFile 标记
tsc --outFile demo.js .\Animal.ts .\Cat.ts .\Dog.ts .\index.ts
方式二
我们可以编译每一个文件(默认方式),那么每个源文件都会对应生成一个 JavaScript 文件。 然后,在页面上通过 <script> 标签把所有生成的 JavaScript 文件按正确的顺序引进来
<script src="./Animal.js"></script> <script src="./Cat.js"></script> <script src="./Dog.js"></script> <script src="./index.js"></script>
模块1
从 ECMAScript 2015 开始,JavaScript 引入了模块的概念。TypeScript 也沿用这个概念
模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用 export 形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用 import 形式之一
interface Animal{ name:string } class Cat implements Animal{ name: string constructor(name:string){ this.name = name; } sayHi(){ console.log(this.name) } } class Dog implements Animal{ name: string constructor(name:string){ this.name = name } sayHello(){ console.log(this.name) } } const c = new Cat("猫") c.sayHi() const d = new Dog("狗") d.sayHello()
我们用模块化的形式实现
// Animal.ts export interface Animal{ name:string }
// Cat.ts import { Animal } from "./Animal" export class Cat implements Animal{ name: string constructor(name:string){ this.name = name; } sayHi(){ console.log(this.name) } }
// Dog.ts import { Animal } from "./Animal" export class Dog implements Animal{ name: string constructor(name:string){ this.name = name } sayHello(){ console.log(this.name) } }
// index.ts import { Cat } from "./Cat" import { Dog } from "./Dog" const c = new Cat("猫") c.sayHi() const d = new Dog("狗") d.sayHello()
模块2
模块化的优势不言而喻,换句话说,如果一个语言无法支持模块化,那么他就无法做大型应用程序的开发
接下来我们在来了解一些模块的其他知识
别名
当导入的名字特别长,或者不容易写的时候,可以使用别名
import { Animal as AL } from "./Animal" export class Cat implements AL{ name: string constructor(name:string){ this.name = name; } sayHi(){ console.log(this.name) } }
默认导出
每个模块都可以有一个 default 导出。 默认导出使用 default 关键字标记;并且一个模块只能够有一个 default 导出
export default interface Animal{ name:string }
import Animal from "./Animal" export class Cat implements Animal{ name: string constructor(name:string){ this.name = name; } sayHi(){ console.log(this.name) } }
导入整个模块
当导出的对象特别多,需要导入的也很多,这个时候,可以使用导入整个模块的方式
export interface Animal{ name:string } export interface AnimalInfo{ age:number }
import * as AN from "./Animal" export class Cat implements AN.Animal,AN.AnimalInfo{ name: string age:number constructor(name:string,age:number){ this.name = name; this.age = age } sayHi(){ console.log(this.name,this.age) } }
声明文件简介
typescript中以.d.ts 为后缀的文件被称为声明文件当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
声明文件分为三种类型
1、typescript内置声明文件
2、第三方声明文件
3、自定义声明文件
什么是声明语句
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了
$('#foo'); // or jQuery('#foo');
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西
jQuery('#foo'); // ERROR: Cannot find name 'jQuery'.
这时,我们需要使用 declare var 来定义它的类型
declare var jQuery: (selector: string) => any; jQuery('#foo');
declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查
什么是声明文件
通常我们会把声明语句放到一个单独的文件( jQuery.d.ts )中,这就是声明文件
//jQuery.d.ts declare var jQuery: (selector: string) => any;
// index.ts jQuery('#foo');
温馨提示
声明文件必需以 .d.ts 为后缀