亲手码出TypeScript最前沿的教程(基础篇)

编者荐语: 本文旨在帮助你们在闲暇时间掌握TS用法,故抽出时间,学习整理TypeScript教程,本文大部份内容来自阮一峰老师的网站,你们放心阅读。javascript

如下内容来自于TypeScript 入门教程html

什么是TypeScript

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。 根据官网翻译成中文是前端

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

为何选择TypeScript

TypeScript 官网列举了一些优点,不过阮一峰老师愿意本身总结一下:web

TypeScript 增长了代码的可读性和可维护性

  • 类型系统其实是最好的文档,大部分的函数看看类型的定义就能够知道如何使用了
  • 能够在编译阶段就发现大部分错误,这总比在运行时候出错好
  • 加强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、代码重构等

TypeScript 很是包容

  • TypeScript 是 JavaScript 的超集,.js 文件能够直接重命名为 .ts 便可
  • 即便不显式的定义类型,也可以自动作出类型推论
  • 即便 TypeScript 编译报错,也能够生成 JavaScript 文件
  • 兼容第三方库,即便第三方库不是用 TypeScript 写的,也能够编写单独的类型文件供 TypeScript 读取

TypeScript 拥有活跃的社区

  • 大部分第三方库都有提供给 TypeScript 的类型定义文件
  • Angular、Vue、VS Code、Ant Design 等等耳熟能详的项目都是使用 TypeScript 编写的
  • TypeScript 拥抱了 ES6 规范,支持 ESNext 草案中处于第三阶状态(Stage 3)的特性

安装TypeScript

TypeScript 的命令行工具安装方法以下:面试

npm install -g typescript
复制代码

以上命令会在全局环境下安装 tsc 命令,安装完成以后,咱们就能够在任何地方执行 tsc 命令了。
编译一个 TypeScript 文件很简单:typescript

tsc hello.ts
复制代码

查看 TypeScript 的版本信息:npm

tsc -v
复制代码

咱们约定使用 TypeScript 编写的文件以 .ts 为后缀,用 TypeScript 编写 React 时,以 .tsx 为后缀。数组

接下来让咱们全面拥抱TypeScript伟大的语言吧~浏览器

原始数据类型

JavaScript 的类型分为两种: 原始数据类型对象类型
如下部分介绍前五中原始数组类型在TypeScipt中的应用。

布尔值

在 TypeScript 中,使用 boolean 定义布尔值类型:

let isDone: boolean = false;
// 编译经过
// 后面约定,未强调编译错误的代码片断,默认为编译经过
复制代码

注意,使用构造函数 Boolean 创造的对象不是布尔值:

let createdByNewBoolean: boolean = new Boolean(1);
// Type 'Boolean' is not assignable to type 'boolean'.
复制代码

事实上 new Boolean() 返回的是一个 Boolean 对象

let createdByNewBoolean: Boolean = new Boolean(1);
复制代码

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

数值(同Number)

使用 number 定义数值类型:

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
复制代码

编译结果:

var decLiteral = 6;
var hexLiteral = 0xf00d;
// ES6 中的二进制表示法
var binaryLiteral = 10;
// ES6 中的八进制表示法
var octalLiteral = 484;
var notANumber = NaN;
复制代码

其中 0b1010 和 0o744 是 ES6 中的 二进制八进制 表示法,它们会被编译为十进制数字。

字符串(同Number)

使用 string 定义字符串类型:

let myName: string = 'Tom';
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${myName}. I'll be ${myAge + 1} years old next month.`;
复制代码

空值(void)

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

function alertName(): void {
  alert('My name is Tom');
}
复制代码

声明一个 void 类型的变量没有什么用,由于你只能将它赋值为 undefinednull

let unusable: void = undefined;
复制代码

如下的两点Null和Undefined尤其须要注意一下,由于在js面试中也常问到。

Null和Undefined

在 TypeScript 中,可使用 null 和 undefined 来定义这两个原始数据类型:

let u: undefined = undefined;
let n: null = null;
复制代码

void 的区别是, undefinednull 是全部类型的 子类型 。也就是说 undefined 类型的变量,能够赋值给 number 类型的变量:

// 这样不会报错
let num: number = undefined;

// 这样也不会报错
let u:undefined;
let num:number = u;
复制代码

void 类型的变量不能赋值给 number 类型的变量,会报错

let u: void;
let num: number = u;
// Type 'void' is not assignable to type 'number'.
复制代码

任意值

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

什么是任意值类型

若是是一个普通类型,在赋值过程当中改变类型是不被容许的:

let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

// Type 'number' is not assignable to type 'string'.
复制代码

但若是是 any 类型,则容许被赋值为任意类型。

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');
复制代码

等价于

let something: any;
something = 'seven';
something = 7;

something.setName('Tom');
复制代码

任意值的弊端

任意值必定要慎用,由于在声明any以后。它的任何操做,返回的类型均是 any 对变量类型的检查变为毫无心义,同时这也就失去了TypeScripe强类型检查语言的意义了。

场景1

模块的引入多是由第三方库引进,没法确认变量的类型时候,应该用 any(不肯定性类型) - 容许赋值为任意类型

let notSure: any = 4
notSure = 'maybe it is a string'
notSure = true

notSure.myName
notSure.getName() // 以上不会报任何错
复制代码

接下来说一下TypeScript与JavaScript不一样的概念

联合类型

联合类型表示取值能够为多种类型中的一种。

简单的例子

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
复制代码
let myFavoriteNumber: string | number;
myFavoriteNumber = true;

// Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
复制代码

联合类型使用 | 分隔每一个类型。 这里的 let myFavoriteNumber: string | number 的含义是,容许 myFavoriteNumber 的类型是 string 或者 number ,可是不能是其余类型。

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

当 TypeScript 不肯定一个联合类型的变量究竟是哪一个类型的时候,咱们<b>只能访问此联合类型的全部类型里共有的属性或方法:</b>
function getLength(something: string | number): number {
  return something.length;
}
复制代码
function getLength(something: string | number): number {
  return something.length;
}

// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
复制代码

上例中, length 不是 stringnumber 的共有属性,因此会报错。

访问 string 和 number 的共有属性是没问题的:

function getString(something: string | number): string {
  return something.toString();
}
复制代码

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错

// Property 'length' does not exist on type 'number'.
复制代码

上例中,第二行的 myFavoriteNumber 被推断成了 string ,访问它的 length 属性不会报错。

而第四行的 myFavoriteNumber 被推断成了 number ,访问它的 length 属性时就报错了。

对象的类型————接口

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

什么是接口

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动须要由类(classes)去实现(implement)。

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

简单的例子

这里注意接口里面定义的对象要用“;”表示,与JS不一样
interface Person {
  name: string;
  age: number;
}

let tom:Person = {
  name: 'Tom',
  age: 25
}
复制代码

上面的例子中,咱们定义了一个接口 Person ,接着定义了一个变量 tom ,它的类型是 Person 。这样,咱们就约束了 tom 的形状必须和接口 Person 一致。

接口通常大写,如 Person

定义的变量比接口少了一些属性是不容许的:

interface Person {
  name: string;
  age: number;
}

let tom: Person = {
  name: 'Tom'
};

// Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
复制代码

定义的变量比接口多一些属性也是不容许的:

interface Person {
  name: string;
  age: number;
}

let tom: Person = {
  name: 'Tom',
  age: 25,
  gender: 'male'
};

// Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
复制代码

可选属性

有时咱们但愿不要彻底匹配一个形状,那么能够用可选属性:

interface Person {
  name: string;
  age?: number;
}

let tom:Person = {
  name: 'Tom'
}
复制代码

可选属性的含义就是该属性能够不存在。
这是仍然不容许添加未定义的属性

interface Person {
  name: string;
  age?: number;
}

let tom:Person = {
  name: 'Tom',
  age: 25,
  gender: 'male'
}

// Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
复制代码

任意属性

有时候咱们但愿一个接口容许有任意的属性,可使用以下方式:

interface Person {
  name: string;
  age?: number;
  [propName: string]: any
}

let tom: Person = {
  name: 'Tom',
  gender: 'male'
}
复制代码

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

须要注意的是,一旦定义了任意属性,那么肯定属性和可选属性的类型都必须是它的类型的子集

错误写法:

interface Person {
  name: string;
  age?: number;
  [propName: string]: string;
}

let tom: Person = {
  name: 'Tom',
  age: 25,
  gender: 'male'
};
复制代码

上例中,任意属性的值容许是 string ,可是可选属性 age 的值倒是 numbernumber 不是 string 的子属性,因此报错了。

一个接口中只能定义一个任意属性。若是接口中有多个类型的属性,则能够在任意属性中使用联合类型:

正确写法:

interface Person {
  name: string;
  age?: number;
  [propName: string]: string | number;
}

let tom:Person = {
  name: 'Tom',
  age: 25,
  gender: 'male'
}
复制代码

只读属性

有时候咱们但愿对象中的一些字段只能在建立的时候被赋值,那么能够用 readonly 定义只读属性:

interface Person {
  readonly id: number;
  name: string;
  age?: number;
  [propName: string]: any;
}

let tom: Person = {
  id: 89757,
  name: 'Tom',
  gender: 'male'
};

tom.id = 9527;
// Cannot assign to 'id' because it is a constant or a read-only property.
复制代码

上例中,使用 readonly 定义的属性 id 初始化后, 又被赋值 了,因此报错了。

注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:

interface Person {
  readonly id: number;
  name: string;
  age?: number;
  [propName: string]: any;
}

let tom: Person = {
  name: 'Tom',
  gender: 'male'
};

tom.id = 89757;

// 两处报错
复制代码

报错信息有两处:

  1. 在对 tom 进行赋值的时候,没有给 id 赋值。
  2. 在给 tom. id 赋值的时候,因为它是 只读属性 ,因此报错了。

数组的类型

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

「类型 + 方括号」

表示法 --- 最简单的数组表示法

let fibonacci: number[] = [1, 1, 2, 3, 5];
复制代码

数组的项中不容许出现其余的类型:

let fibonacci: number[] = [1, '1', 2, 3, 5];

// Type 'string' is not assignable to type 'number'.
复制代码

数组的一些方法参数也会根据数组在定义时约定的类型进行限制:

let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');

// Argument of type '"8"' is not assignable to parameter of type 'number'.
复制代码

上例中,push 方法只容许传入 number 类型的参数,可是却传了一个 "8" 类型的参数,因此报错了。这里 "8" 是一个字符串字面量类型,会在后续章节中详细介绍。

用接口表示数组

interface NumberArray {
  [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
复制代码

NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。

虽然接口也能够用来描述数组,可是咱们通常不会这么作,由于这种方式比前两种方式复杂多了。

不过有一种状况例外,那就是它经常使用来表示类数组。

类数组 - arguments

function sum() {
  let args: number[] = arguments;
}

// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.
复制代码

上例中,arguments 其实是一个类数组,不能用普通的数组的方式来描述,而应该用接口:

function sum(){
  let args: {
    [index: number]: number;
    length: number;
    callee: Function;
  } = arguments;
}
复制代码

在这个例子中,咱们除了约束当索引的类型是数字时,值的类型必须是数字以外,也约束了它还有 lengthcallee 两个属性。
事实上经常使用的类数组都有本身的接口定义,如 IArguments, NodeList, HTMLCollection 等:

function sum() {
  let args: IArguments = arguments;
}
复制代码
interface IArguments {
  [index: number]: any;
  length: number;
  callee: Function;
}
复制代码

any在数组中的应用

一个比较常见的作法是,用 any 表示数组中容许出现任意类型:

let list: any[] = ['abcd', 25, { website: 'http://xcatliu.com' }]
复制代码

函数的类型

函数声明

在 JavaScript 中,有两种常见的定义函数的方式——函数声明和函数表达式:

// 函数声明
function sum(x,y) {
  return x + y;
}

// 函数表达式
let mySum = function(x, y) {
  return x + y;
}
复制代码

函数声明的类型定义

限制了参数的类型 和 返回值的 类型

function sum(x: number, y:number):number {
  return x + y;
}
复制代码

注意,输入多余的(或者少于要求的)参数,是不被容许的:

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

function sum(x: number, y: number): number {
  return x + y;
}
sum(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');

// A required parameter cannot follow an optional parameter.
复制代码

参数默认值

TypeScript 会将添加了默认值的参数识别为可选参数: 不传的话,默认值为 Cat

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');
复制代码

剩余参数

items 是一个数组。因此咱们能够用数组的类型来定义它:

function push(array: any[], ...items: any[]) {
  items.forEach(function(item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);
复制代码

函数表达式

经过对函数声明式学习的了解,咱们可能会写成这样:

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 的类型定义中,=> 用来表示函数的定义,左边是输入类型,须要用括号括起来,右边是输出类型

用接口定义函数的形状(拓展)

咱们也可使用接口的方式来定义一个函数(参数)(输出值)须要符合的形状:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc; 
mySearch = function(source: string, subString: string) {
  return souce.search(subString) !== -1;
}
复制代码

采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,能够保证之后对函数名赋值时保证参数个数、参数类型、返回值类型不变

看完三件事❤

若是你以为这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  1. 点赞,转发让更多的人也能看到介绍内容(收藏不点赞,皆是耍流氓!!)
  2. 关注公众号 “前端时光屋”,不按期分享原创知识。
  3. 同时能够期待后续文章ing

也能够来个人我的博客:

前端时光屋:www.javascriptlab.top/

相关文章
相关标签/搜索