TypeScript学习笔记

(一) 基础类型

什么是TypeScript

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

官方定义node

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source.es6

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

为何要学习 TypeScript

  • Typescript 是对 JavaScript 的加强,它大大加强了代码的可读性和维护性,让咱们编写出更加健壮的代码shell

  • 将来前端的发展趋势npm

    • 最新发布的 Vue3 使用了TypeScript。
    • Angular 在 2.0 版本就内置了 TypeScript
    • React 对TypeScript 的支持也很丝滑

开始学习TypeScript

类型注解

学习TypeScript以前咱们先来了解类型注解这个概念TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。编程

// js
let num = 5 
num = 'hello' // 没毛病

// ts
let num: number = 5 
num = 'hello' // 报错,由于定义了num为number类型的变量因此赋值为string类型时会报错
复制代码
复制代码

而后咱们来看看TypeScript中的基本类型json

  • 布尔类型(boolean)windows

  • 数字类型(number)数组

  • 字符串类型(string)

  • 数组类型(array)

  • 元组类型(tuple)

  • 枚举类型(enum)

  • 任意值类型(any)

  • null 和 undefined

  • void类型

  • never类型

  • object 类型

  • 类型断言

  • Symbol 类型

布尔值

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫作boolean(其它语言中也同样)。

let bool: boolean = true
复制代码
复制代码

数字

和JavaScript同样,TypeScript里的全部数字都是浮点数。 这些浮点数的类型是number。 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。

let num: number = 123
num = 0b1101 // 二进制
num = 0o164 // 八进制
num = 0x8b // 十六进制
复制代码
复制代码

字符串类型

和JavaScript同样,可使用双引号(")或单引号(')表示字符串。

let name: string = 'hui'
复制代码
复制代码

你还可使用_模版字符串_,它能够定义多行文本和内嵌表达式。 这种字符串是被反引号包围( **** ),而且以${ expr }`这种形式嵌入表达式

let name: string = `hui`
let hello: string = `hello, my name is ${name}`
复制代码
复制代码

数组

TypeScript像JavaScript同样能够操做数组元素。

有两种方式能够定义数组。 第一种,能够在元素类型后面接上[],表示由此类型元素组成的一个数组:

let arr1: number[] = [1, 2, 3]
let arr2: string[] = ['a', 'b', 'c']
let arr3: (number | string)[] = [1, 'b', 3] // 数组元素既能够是number类型也能够是string类型
复制代码
复制代码

第二种方式是使用数组泛型,Array<元素类型>

let arr1: Array<number> = [1, 2, 3]
let arr2: Array<string> = ['a', 'b', 'c']
let arr3: Array<number | string> = [1, 'b', 2]
复制代码
复制代码

元组 Tuple

元组类型容许表示一个已知元素数量和类型的数组,各元素的类型没必要相同。 好比,你能够定义一对值分别为stringnumber类型的元组。

let x: [string, number]
x = ['hui', 2] // OK
x = [2, 'hui'] // Error
复制代码
复制代码

枚举类型

enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言同样,使用枚举类型能够为一组数值赋予友好的名字。

enum Roles {
  SUPER_ROLE,
  ADMIN,
  USER
}
console.log(Roles.SUPER_ROLE) // 0
console.log(Roles.ADMIN) // 1
console.log(Roles.USER) // 2
复制代码
复制代码

默认状况下,从0开始为元素编号。 你也能够手动的指定成员的数值。 例如,咱们将上面的例子改为从1开始编号:

enum Roles {
  SUPER_ROLE = 1,
  ADMIN,
  USER
}
console.log(Roles.SUPER_ROLE) // 1
console.log(Roles.ADMIN) // 2
console.log(Roles.USER) // 3
复制代码
复制代码

任意值 any

有时候,咱们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,好比来自用户输入或第三方代码库。 这种状况下,咱们不但愿类型检查器对这些值进行检查而是直接让它们经过编译阶段的检查。 那么咱们可使用any类型来标记这些变量:

let name:any = 'hui'
name = 123 // OK
name = true // OK
复制代码
复制代码

当你只知道一部分数据的类型时,any类型也是有用的。 好比,你有一个数组,它包含了不一样的类型的数据:

let list: any[] = [1, true, 'hui']
复制代码
复制代码

尽可能少的使用 any, 不然你可能在用 AnyScript 写代码

空值

某种程度上来讲,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你一般会见到其返回值类型是void

function getName():void {
  alert('my name is hui')
}
复制代码
复制代码

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

let v: void
v = undefined // OK
复制代码
复制代码

Null 和 Undefined

TypeScript里,undefinednull二者各自有本身的类型分别叫作undefinednull。 和void类似,它们的自己的类型用处不是很大:

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

Never

never类型表示的是那些永不存在的值的类型。never 类型用于返回 error 死循环

function getError(message: string): never {
    throw new Error(message);
}
function infiniteFunc(): never {
    while (true) {}
}
复制代码
复制代码

never类型是任何类型的子类型,也能够赋值给任何类型;然而,_没有_类型是never的子类型或能够赋值给never类型(除了never自己以外)。 即便any也不能够赋值给never

let neverVarible: never = (() => {
  while (true) {}
})()
let num: number = 123
let name: string = 'hui'
num = neverVarible // OK
neverVarible = name // error
复制代码
复制代码

类型断言

类型断言就是手动指定一个类型的值

类型断言有两种形式。 其一是“尖括号”语法:

let str: any = "this is a string";

let strLength: number = (<string>str).length;
复制代码
复制代码

另外一个为as语法:

let str: any = "this is a string";

let strLength: number = (str as string).length;
复制代码
复制代码

(二) 接口

什么是接口(interface)

TypeScript的核心原则之一是对值所具备的_结构_进行类型检查。 它有时被称作“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的做用就是为这些类型命名和为你的代码或第三方代码定义契约。

基本用法

经过一个简单的例子来看看接口是如何工做的

function getFullName(fullName: {firstName: string, lastName: string}) {
  return `${fullName.firstName} ${fullName.lastName}`
}
let fullName = {
  firstName: 'li',
  lastName: 'hh',
  age: 18
}
getFullName(fullName)
复制代码
复制代码

类型检查器会查看getInfo的调用。 getInfo有一个参数,并要求这个对象参数有一个名为firstName类型为 string 和名为 lastName 类型为 string 的属性。 须要注意的是,咱们传入的对象参数实际上会包含不少属性,可是编译器只会检查那些必需的属性是否存在,而且其类型是否匹配。 然而,有些时候TypeScript却并不会这么宽松,咱们下面会稍作讲解。

下面咱们重写上面的例子,此次用接口描述:必须包含 firstNamelastName 属性

interface FullName {
  firstName: string,
  lastName: string
}
function getFullName(fullName: FullName) {
  return `${fullName.firstName} ${fullName.lastName}`
}
let fullName = {
  firstName: 'li',
  lastName: 'hh'
}
getFullName(fullName)
复制代码
复制代码

FullName 接口就比如是一个名字,用来描述例子里的要求,他表示了有 firstNamelastName 而且类型为 string 的对象。还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在而且类型也是对的就能够。

接口的名字首字母要大写

可选属性

接口里的属性有时候不都是必须的,有时候是可选的,咱们用 ? 用来定义可选属性

interface Fruit {
  type: string,
  color?: string
}
const getFruit({type, color}: Fruit):string {
  return `A ${color ? (color + ' ') : ''} ${type}`
}
getFruit({
  color: 'red',
  type: 'apple'
}) // ok
getFruit({
  type: 'apple'
}) // ok
复制代码
复制代码

多余属性检查

仍是上面那个例子,若是咱们在传递的参数中多加一个属性,例如

getFruit({
  color: 'red',
  type: 'apple',
  size: 19
}) // err
复制代码
复制代码

这个时候 TypeScript 会告诉咱们传的参数多了一个 size 的属性,可是其实这个是不影响咱们的结果的,这个时候有两种办法来解决这个问题。

第一种使用类型断言

getFruit({
  color: 'red',
  type: 'apple',
  size: 19
} as Fruit) // ok
复制代码
复制代码

第二种使用索引签名

interface Fruit {
  type: string,
  color?: string,
  [prop: string]: any
}
const getFruit({type, color}: Fruit):string {
  return `A ${color ? (color + ' ') : ''} ${type}`
}
getFruit({
  color: 'red',
  type: 'apple',
  size: 19
} as Fruit) // ok
复制代码
复制代码

只读属性

接口还能够设置只读属性,表示这个属性不可被修改

interface Fruit {
  type: string,
  readonly color: string
}
const apple: Fruit = {
  type: 'apple',
  color: 'red'
}
apply.color = 'green' // err
复制代码
复制代码

函数类型

接口不只能够定义对象的形式,还能够定义函数形式

interface AddFunc {
  (number1: number, number2: number) => number
}
const addFunc: AddFunc = (n1, n2) => {
  return n1 + n2
}

复制代码
复制代码

索引类型

接口还定义索引类型

interface ArrInter {
  0: string,
  1: number
}
const arr: ArrInter = ['a', 1]
复制代码
复制代码

继承接口

一个接口能够继承另外一个接口, 使用 extends 关键字来实现继承

interface Fruit {
	type: string
}
interface Apple extends Fruit {
  color: string
}
cosnt apple: Apple = {
  color: 'red'
} // 报错, 由于继承 Fruit 接口因此必须有 type 属性
复制代码
复制代码

混合类型接口

接口还能够定义包含任意类型属性的接口

interface Counter {
  (): void, // 一个函数
  count: number
}
const getCounter = ():Counter => {
  const c = () => c.count++
  c.count = 1
  return c
}
const counter: Counter = getCounter()
counter()
console.log(counter.count) // 2
counter()
console.log(counter.count) // 3
counter()
console.log(counter.count) // 4
复制代码
复制代码

(三) 类

基本例子

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");
复制代码
复制代码

咱们声明了一个Greeter类,它包含三个成员:一个greeting属性,一个greet方法,一个构造函数。最后一行,咱们使用new构造了Greeter类的一个实例。 它会调用以前定义的构造函数,建立一个Greeter类型的新对象,并执行构造函数初始化它。

继承

类容许继承,基本例子

class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
复制代码
复制代码

在这个例子中,类从基类中继承了属性和方法,在这里,Dog是一个派生类,派生自Animal基类,派生类一般被称做子类,基类一般被称做超类。 复杂例子

class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Horse extends Animal { 
    constructor(name: string) { 
      // 必须先调用super函数 
      super(name); 
    }
    // 重写父类的方法
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let tom: Animal = new Horse("Tommy the Palomino");
tom.move(34);
复制代码
复制代码

在这个例子中,子类中多了构造函数,当子类中包含构造函数时,必须 要先调用super(),它会执行超类的构造函数,而且必定要在构造函数访问this以前。子类还能够重写父类的方法。 因此,tom虽然被声明为animal类型,但它的值时House,因此执行的move方法是House中的move方法。

Galloping...
Tommy the Palomino moved 34m.
复制代码
复制代码

公共,私有与受保护的修饰符

public

默认为public,不须要特别去标记

private

私有属性不能够在类的外部被访问。

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // 错误: 'name' 是私有的.
复制代码
复制代码
protected

protected属性与私有属性比较类似,不一样的是,protected能够在派生类中被访问,

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name)
        this.department = department;
    }

    public getElevatorPitch() {
        // 派生类能够访问父类中的protected属性
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误,protected属性不可在类的外部访问。
复制代码
复制代码
readonly

只读属性,只读属性必须在声明时或构造函数里被初始化

参数属性
class Animal {
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}
复制代码
复制代码

将原来须要在构造函数声明以前定义的属性直接在构造函数里使用private name: string参数来建立和初始化name成员,把声明和赋值合并至一处。

存取器

TypeScript支持经过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。

let passcode = "secret passcode";

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}
复制代码
复制代码

首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有get不带有set的存取器自动被推断为readonly。

静态属性

静态属性是存在于类自己的成员,而不是在类的实例中,不须要在类的实例化时才被初始化。 访问静态属性,须要在属性名前加上类名,如同在实例属性上使用this.前缀来访问属性同样。

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
复制代码
复制代码

抽象类

抽象类通常是其余派生类的基类,通常不会实例化。 abstract 关键字主要是用于定义抽象类和抽象类中的方法,它的语法与接口的语法很类似,二者都是定义方法签名但不包含方法体。 不一样于接口的是:抽象类中的抽象方法不包含具体实现但必须在派生类中实现;抽象类能够包含成员的实现细节;抽象方法必须包含abstract关键字而且能够包含访问修饰符。

abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log('Department name: ' + this.name);
    }

    abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }
    
    // 定义一个抽象基类中没有声明的方法,可是它没办法在实例中使用
    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department; // 容许建立一个对抽象类型的引用
department = new Department(); // 错误: 不能建立一个抽象类的实例
department = new AccountingDepartment(); // 容许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在
复制代码
复制代码

高级类

构造函数

当你在TypeScript里声明了一个类的时候,实际上同时声明了不少东西。 首先就是类的实例的类型。

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}
// 声明greeter类的实例的类型是Greeter
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
复制代码
复制代码

这里,咱们写了let greeter: Greeter,意思是Greeter类的实例的类型是Greeter。

咱们也建立了一个叫作构造函数的值。 这个函数会在咱们使用new建立类实例的时候被调用。 下面咱们来看看,上面的代码被编译成JavaScript后是什么样子的:

let Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
复制代码
复制代码

上面的代码里,let Greeter将被赋值为构造函数。 当咱们调用new并执行了这个函数后,便会获得一个类的实例。 这个构造函数也包含了类的全部静态属性。 换个角度说,咱们能够认为类具备实例部分与静态部分这两个部分。

(四) 开始使用 TypeScript

在开始使用 TypeScript 前你最好有如下准备:

  • Node.js > 8.0,最好是最新的稳定版(目前是V10.16.3 )
  • 一个包管理工具 npm 或者 yarn
  • 一个文本编辑器或者 IDE (笔者的是 vscode)

相关的 shell 命令仅适用于 *nix 系统,windows 系统不适用

安装 TypeScript

TypeScript 的安装很简单,你能够经过npm直接在全局安装 TypeScript。

> npm install -g typescript
复制代码

建立环境

随后咱们要建立一个目录:

mkdir ts-study && cd ts-study
复制代码

接着建立 src 目录:

mkdir src && touch src/index.ts
复制代码

接着咱们用npm将目录初始化:

npm init
复制代码

此时咱们要使用 TypeScript 的话一般也须要初始化:

tsc --init
复制代码

这个时候你会发现目录下多了一个tsconfig.json文件.

这是 TypeScript 的配置文件,里面已经包含官方初始化的一些配置以及注释,咱们如今进行自定义的配置:

{
  "compilerOptions": {
    "target": "es5",                            // 指定 ECMAScript 目标版本: 'ES5'
    "module": "commonjs",                       // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "moduleResolution": "node",                 // 选择模块解析策略
    "experimentalDecorators": true,             // 启用实验性的ES装饰器
    "allowSyntheticDefaultImports": true,       // 容许从没有设置默认导出的模块中默认导入。
    "sourceMap": true,                          // 把 ts 文件编译成 js 文件的时候,同时生成对应的 map 文件
    "strict": true,                             // 启用全部严格类型检查选项
    "noImplicitAny": true,                      // 在表达式和声明上有隐含的 any类型时报错
    "alwaysStrict": true,                       // 以严格模式检查模块,并在每一个文件里加入 'use strict'
    "declaration": true,                        // 生成相应的.d.ts文件
    "removeComments": true,                     // 删除编译后的全部的注释
    "noImplicitReturns": true,                  // 不是函数的全部返回路径都有返回值时报错
    "importHelpers": true,                      // 从 tslib 导入辅助工具函数
    "lib": ["es6", "dom"],                      // 指定要包含在编译中的库文件
    "typeRoots": ["node_modules/@types"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": [                                  // 须要编译的ts文件一个*表示文件匹配**表示忽略文件的深度问题
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "**/*.test.ts",
  ]
}
复制代码

而后在package.json中加入咱们的script命令:

{
  "name": "ts-study",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "build": "tsc", // 编译
    "build:w": "tsc -w" // 监听文件,有变更即编译
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript ": "^3.6.4"
  }
}
复制代码

编写第一个 TypeScript 程序

src/index.ts中输入如下代码:

function greeter(person) {
    return "Hello, " + person
}

const user = "Jane User"
复制代码

这个时候你会看到一个警告,这个警告在官方默认配置中是不会出现的,正是因为咱们开启了 noImplicitAny 选项,对于隐式含有 any 类型的参数或者变量进行警告⚠️.

2019-06-25-00-57-51

之因此一开始就开启严格模式,是由于一旦你开始听任any类型的泛滥,就会把 TypeScript 变成 AnyScript ,会很难改掉这个恶习,因此从一开始就要用规范的 TypeScript 编码习惯。

咱们进行修改以下:

function greeter(person: string) {
    return "Hello, " + person
}
复制代码

此时咱们能够看到,greeter函数自动加上了返回值类型,这是 TypeScript 自带的_类型推导_。

2019-06-25-01-08-12

参考学习:

深刻浅出TypeScript:从基础知识到类型编程

相关文章
相关标签/搜索