Typescript简明教程

517 阅读9分钟

原始类型

// 布尔值
const isLoading: boolean = false
// 数字
const decLiteral: number = 6
// 字符串
const book: string = '深入浅出typescript'
// 空值
function warnUser(): void {
    alert("This is my warning message");
}
const a: void = undefined
// Null 和 undefined
let a: undefined = undefinded;
let b: null = null;
// symbol
const sym1 = Symbol('key1');
// Bigint
const max = BigInt(Number.MAX_SAFE_INTEGER);

其他常见类型

any

any 指在编程阶段还不清楚类型的变量指定一个类型。

// 一般不考虑使用any!!!
let notSure: any = 4;
notSure = "maybe a string instead";

unkown

unkown是 any 类型对应的安全类型

// unkonwn 和 any 都为任何类型
// 当 unknown 类型被确定是某个类型之前,它不能被进行任何操作比如实例化、getter、函数执行等等。
let value: unknown;
value.foo.bar;  // ERROR
value();        // ERROR
new value();    // ERROR
value[0][1];    // ERROR

never

never 类型表示的是那些永不存在的值的类型

// 抛出异常的函数永远不会有返回值
function error(message: string): never {
    throw new Error(message);
}

// 空数组,而且永远是空的
const empty: never[] = []

数组

// 1. 泛型
const list: Array<number> = [1, 2, 3]
// 2. 接[]
const list: number[] = [1, 2, 3]

元祖

表示一个已知元素数量和类型的数组,各元素的类型不必相同。

// 元组的类型如果多出或者少于规定的类型是会报错的
let x: [string, number];
x = ['hello', 10, false] // Error
x = ['hello'] // Error

// 类型的顺序不一样也会报错。
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error

元组继承于数组,但是比数组拥有更严格的类型检查。

interface Tuple extends Array<string | number> {
  0: string;
  1: number;
  length: 2;
}

Object

object 表示非原始类型,也就是除 number,string,boolean,symbol,null 或 undefined 之外的类型。

枚举类型

用于声明一组命名的常数,一个变量有几种可能的取值

数字枚举

// 默认从0开始依次累加
// 当第一个值赋值后,后面也会根据第一个值进行累加:
enum Direction {
    Up = 10,
    Down,
    Left,
    Right
}

console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13

字符串枚举

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

console.log(Direction['Right'], Direction.Up); // Right Up

异构枚举

不推荐!!!

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

本质

在编译后, 因为 Direction[Direction["Up"] = 10] = "Up" 也就是 Direction[10] = "Up" ,所以我们可以把枚举类型看成一个JavaScript对象,而由于其特殊的构造,导致其拥有正反向同时映射的特性。

(感悟:ts中的枚举类型和普通的js对象本质上没有区别,只是对于开发者来说,相较于直接使用值类型去做判断,枚举类型更易读,能够提升代码的可读性和易维护性。)

enum Direction {
    Up = 10,
    Down,
    Left,
    Right
}

console.log(Direction[10], Direction['Right']); // Up 13

// 编译后
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 10] = "Up";
    Direction[Direction["Down"] = 11] = "Down";
    Direction[Direction["Left"] = 12] = "Left";
    Direction[Direction["Right"] = 13] = "Right";
})(Direction || (Direction = {}));

常量枚举

const enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

const a = Direction.Up;

// 编译后
var a = "Up";

枚举的每个成员和枚举值本身都可以作为类型来使用

enum Direction {
    Up,
    Down,
    Left,
    Right
}

const a = 0

console.log(a === Direction.Up) // true

联合枚举类型

我们把 a 声明为 Direction 类型,可以看成我们声明了一个联合类型 Direction.Up | Direction.Down | Direction.Left | Direction.Right,只有这四个类型其中的成员才符合要求。

enum Direction {
    Up,
    Down,
    Left,
    Right
}

declare let a: Direction

enum Animal {
    Dog,
    Cat
}

a = Direction.Up // ok
a = Animal.Dog // 不能将类型“Animal.Dog”分配给类型“Direction”

枚举合并

我们可以分开声明枚举,他们会自动合并

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

enum Direction {
    Center = 1
}

接口Interface

接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

// 接口不会去检查属性的顺序,只要相应的属性存在并且类型兼容即可。
interface User {
    name: string
    age?: number // 可选属性
    readonly isMale: boolean // 只读属性
    say: (words: string) => string // 函数类型
}

const getUserName = (user: User) => user.name

// 接口直接描述函数类型
interface Say {
    (words: string) : string
}

属性检查

interface Config {
  width?: number;
}
function  CalculateAreas(config: Config): { area: number} {
  let square = 100;
  if (config.width) {
      square = config.width * config.width;
  }
  return {area: square};
}
let mySquare = CalculateAreas({ widdth: 5 });
// 注意我们传入的参数是 widdth,并不是 width。 此时TypeScript会认为这段代码可能存在问题。

// 3种解决方案
// 1. 类型断言
let mySquare = CalculateAreas({ widdth: 5 } as Config);
// 2. 添加字符串索引签名
interface Config {
   width?: number;
   [propName: string]: any;
}
// 3. 将字面量赋值给另外一个变量
let options: any = { widdth: 5 };
let mySquare = CalculateAreas(options);

可索引类型

可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

interface Phone {
    [name: string]: string
}

interface User {
    name: string
    age?: number
    readonly isMale: boolean
    say: () => string
    phone: Phone
}

继承接口

interface VIPUser extends User {
    broadcast: () => void
}

// 继承多个接口
interface VIPUser extends User, SupperUser {
    broadcast: () => void
}

继承类

类class

与ES6 class基本用法一致 阮一峰ES6入门

抽象类

抽象类做为其它派生类的基类使用,不能直接实例化抽象类,不同于接口,抽象类可以包含成员的实现细节。

abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

*派生类

派生类从基类中继承了属性和方法

例如,Dog是一个 派生类,它派生自 Animal 基类,通过 extends关键字

派生类通常被称作 子类,基类通常被称作 超类

派生类包含了一个构造函数,必须调用 super(),它会执行基类的构造函数

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

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

// 执行结果:
// Slithering...
// Sammy the Python moved 5m.
// Galloping...
// Tommy the Palomino moved 34m.

访问限定符

public 被此限定符修饰的成员是可以被外部访问

class Car {
    public run() {
        console.log('启动...')
    }
}

const car = new Car()

car.run() // 启动...

private被此限定符修饰的成员是只可以被类的内部访问。

protected 被此限定符修饰的成员是只可以被类的内部以及类的子类访问。

class Car {
    protected run() {
        console.log('启动...')
    }
}

class GTR extends Car {
    init() {
        this.run()
    }
}

const car = new Car()
const gtr = new GTR()

car.run() // [ts] 属性“run”受保护,只能在类“Car”及其子类中访问。
gtr.init() // 启动...
gtr.run() // [ts] 属性“run”受保护,只能在类“Car”及其子类中访问。

存取器

通过getters/setters来截取对对象成员的访问

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!");
        }
    }
}

*与接口异同

interface 接口只声明成员方法,不做实现。

class 类声明并实现方法。

也就是说:interface只是定义了这个接口会有什么,但是没有告诉你具体是什么。

// 每个实现该接口的类都必须实现getContent方法

// implements 的作用和意义到底是什么?
// 意义在于工程化开发
// 比如跨端开发,使用依赖注入模式的话,顶层API依赖各端适配器,那就可以约定好interface,各端分头implements.这样要重构,要加新功能,新端都是非常容易.便于测试,便于开发.

interface ContentInterface {
    getContent(): String;
}

class Article implements ContentInterface {
    // 必须实现getContent方法
    public function getContent(): String {
        return 'I am an article.';
    } 
}

class Passage implements ContentInterface {
    // 但实现方式可以不同
    public function getContent(): String {
        return 'I am a passage.'
    }
}

class News implements ContentInterface {
    // 没有实现getContent方法,编译器会报错
}

function print(obj: ContentInterface): void {
    // 实现了ContentInterface的对象是一定有getContent方法的
    console.log(obj.getContent());
}

let a = new Article();
let p = new Passage();

print(a); // "I am an article."
print(p); // "I am a passage."

函数

一等公民

// 函数表达式
function sum(x: number, y: number): number {
    return x + y;
}
// 函数对于参数的个数是不容修改的。
sum2(1, 2);
sum2(1, 2, 3); // Expected 2 arguments, but got 3.
sum2(1); // Expected 2 arguments, but got 1.

定义函数类型

// Typescript 中的 => 是用来定义函数的,函数左边是是输入类型(用 () 括起来),右侧是输出类型。
const add = (a: number, b: number) => a + b

接口定义

// 1
interface Function5 {
    (x: string, y: string): boolean
}

let function5: Function5 = (x: string, y: string) => {
    return x.search(y) > -1;
}

// 2
interface Function5 {
    x: string,
    y: string
}
let function5 = (params:Function5):Boolean => {
    return x.search(y) > -1;
}

可选参数

const showMyName = (firstName: string, lastName?: string): string => {
    // todo something
}

剩余参数

剩余参数和可选参数后面都不能再有参数

const push = (array: any[], ...rest: any[]) => {
    rest.forEach(r => {
        array.push(r);
    });
}

默认值

const showMyNameAgain = (firstName: string = 'pr', lastName?: string, ...rest: any[]): string => {
    // todo something
}

重载

根据不同数量或类型的参数,做出不同的处理。

/* 注意点
 * 1. 输出值类型需同输入参数类型一致(用到联合类型);
 * 2. 根据参数类型做不同操作处理(用到类型判断);
 * 前 2 次是函数定义,第 3 次是函数实现
 */
function chongzai2(x: string): string;
function chongzai2(x: number): number;
function chongzai2(x: number | string): number | string {
    if(typeof x === 'string') {
        return `hello, ${x}`;
    } else if (typeof x === 'number') {
        return x * 10;
    }
}

泛型

在函数名称后面声明泛型变量 <T>,输入什么类型,输出也是什么类型

function returnItem<T>(para: T): T {
    return para
}
// 多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

泛型变量

function getArrayLength<T>(arg: Array<T>) {
  console.log(arg.length) // error  类型“T”上不存在属性“length”
  // 类型断言
  console.log((arg as Array<any>).length) // ok 
  return arg
}

泛型类

class Stack<T> {
    private arr: T[] = []

    public push(item: T) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

类型注解

function hello<T>(param: T) {
  return parmas;
}
const func:<T>(param: T) => T = hello;

类型断言

// 谨慎使用,强制把某类型断言会造成 TypeScript 丧失代码提示的能力
interface Person {
 name: string;
 age: number;
}

const person = {} as Person;

person.name = 'xiaomuzhu';
person.age = 20;

类型守卫

instanceof 类型保护是通过构造函数来细化类型的一种方式

class Person {
    name = 'xiaomuzhu';
    age = 20;
}

class Animal {
    name = 'petty';
    color = 'pink';
}

function getSometing(arg: Person | Animal) {
    // 类型细化为 Person
    if (arg instanceof Person) {
        console.log(arg.color); // Error,因为arg被细化为Person,而Person上不存在 color属性
        console.log(arg.age); // ok
    }
    // 类型细化为 Person
    if (arg instanceof Animal) {
        console.log(arg.age); // Error,因为arg被细化为Animal,而Animal上不存在 age 属性
        console.log(arg.color); // ok
    }
}

x in y 表示 x 属性在 y 中存在。

class Person {
	name = 'xiaomuzhu';
	age = 20;
}

class Animal {
	name = 'petty';
	color = 'pink';
}

function getSometing(arg: Person | Animal) {
	if ('age' in arg) {
		console.log(arg.color); // Error
		console.log(arg.age); // ok
	}
	if ('color' in arg) {
		console.log(arg.age); // Error
		console.log(arg.color); // ok
	}
}

声明文件

// 声明语句
declare var jQuery: (selector: string) => any;

把声明语句放在一个单独的文件中,这就是声明文件,声明文件必需以.d.ts为后缀

推荐使用 @types 统一管理第三方库的声明文件。

// `@types` 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev

全局变量的声明文件主要有以下几种语法:

接口与类

interface 接口只声明成员方法,不做实现。

class 类声明并实现方法。

也就是说:interface只是定义了这个接口会有什么,但是没有告诉你具体是什么。

类继承

类与类之间只能进行单继承,想要实现多继承需要使用 Mixins 的方式

class Animal {
    public name: string = "Animal";
    public age: number;

    sayHello() {
        console.log(`Hello ${ this.name }`);
    }
}

class Dog extends Animal {
    age: number;

    constructor(age) {
        super();
        this.age = age;
    }
}

const dog = new  Dog(6);
dog.sayHello();


类的多继承

// Person 类
class Person {
    name: string;
    sayHello() {
        console.log('tag', `Helo ${ this.name }!`)
    }
}

// Student 类
class Student {
    grade: number;
    study() {
        console.log('tag', ' I need Study!')
    }
}

// 没使用 extends 而是使用 implements。 把类当成了接口,仅使用 Person 和 Student 的类型而非其实现。 
// 我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。
class SmartObject implements Person, Student {
	// Person
	name: string = 'person';
	sayHello: () => void;
	// Activatable
	grade: number = 3;
	study: () => void;
}



类继承接口

  // 动物接口
  interface Animal {
      name: string;

      eat: () => void;
  }

  // 猫科接口
  interface Felidae {
      claw: number;
      run: () => void;
  }

  // 让猫类实现 Animal 和 Felidae 两个接口
  class Cat implements Animal, Felidae {

      name: string;
      claw: number;

      eat() {
          console.log('tag', 'I love eat Food!');
      }

      run: () {
          console.log('tag', 'My speed is very fast!')
      }
  }

  const dog: Dog = new Dog();
  dog.eat();

接口继承

  • 接口与接口之间可以直接进行多继承
  • 类实现接口可以进行多实现,每个接口用 , 隔开即可
interface VIPUser extends User {
    broadcast: () => void
}

// 继承多个接口
interface VIPUser extends User, SupperUser {
    broadcast: () => void
}

接口继承类

接口继承类也只能进行单继承

class Person {
  name: string
  age: number
}
interface StrategySetStore extends Person {}

implements 实现,一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能

extends 继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法