TS学习

161 阅读4分钟

TypeScript

基本类型

  • 布尔值(boolean)
let isDone: boolean = false;
  • 数值(number)
let decLiteral: number = 6;
  • 字符串(string)
let myName: string = 'Tom';
  • 空值(void) JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数: 声明一个 void 类型的变量没有什么用,因为你只能将它赋为 undefined 和 null
let unusable: void = undefined;
  • Null Undefined 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 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) any 类型,则允许被赋值为任意类型。

联合类型

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

let myFavoriteNumber: string | number;

接口(对象类型)

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

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

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

接口的可选属性

希望不完全匹配某种类型

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

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

任意属性

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

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

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

只读属性

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

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

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

数组类型

  • 使用「类型 + 方括号」来表示数组:
let fibonacci: number[] = [1, 1, 2, 3, 5];
  • 使用数组泛型(Array Generic) Array<elemType> 来表示数组:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • 接口表示数组
interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

函数类型

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

  • 函数声明(Function Declaration)
function sum(x, y) {
    return x + y;
}
  • 函数表达式(Function Expression)
let mySum = function (x, y) {
    return x + y;
};

约束方法

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

let mySum = function (x: number, y: number): number {
    return x + y;
};

接口定义函数

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

let mySearch: SearchFunc;

可选参数

可选参数后面不允许再出现必需参数了

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}

默认值

TypeScript 会将添加了默认值的参数识别为可选参数

function buildName(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}

剩余参数

ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数),用数组的类型来定义它

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

类型断言

用来手动指定一个值的类型。

as 类型

<类型>

类型别名

类型别名用来给一个类型起个新名字。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

类型别名常用于联合类型。

高级类型

  • 元组 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。 确定了索引对应的类型。
let tom: [string, number] = ['Tom', 25];

可以只赋值其中一项:

let tom: [string, number];
tom[0] = 'Tom';

枚举

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

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射: 相当于键值对互相映射的对象。

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

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

我们也可以给枚举项手动赋值:

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

console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

基本概念

  • 类:包含属性和方法,是事物的抽象
  • 对象:类的实例
  • 存取器:改变属性的读取和赋值行为
  • 修饰符:限定成员或类型的性质
  • 抽象类:其他类继承的基类,不允许被实例化,类的方法在子类中被实现(类的地基。自身未实现)
  • 接口:不同类之间共有的属性或方法,可以抽象成接口。一个类只能继承自另一个类,但可以实现那多个接口(共有逻辑的抽离)

基本操作

  • 属性和方法 使用 class 定义类,使用 constructor 定义构造函数。

通过 new 生成新实例的时候,会自动调用构造函数。

class Animal {
    name;
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
}

let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
  • 继承 使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
class Cat extends Animal {
  constructor(name) {
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }
  sayHi() {
    return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
  }
}

let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
  • 静态方法(通过类来调用,而非通过实例调用) 使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
}

let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
  • 修饰符
  1. public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  2. private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
  3. protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的

泛型

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

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']

在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了。(在使用泛型的函数中要在函数后说明有哪些泛型要用,有点类似于变量声明) 在此段代码中。泛型的作用有点类似变量,同一个x,数值未定,但是在用到的每一个地方都是这个x。

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法 这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

泛型接口

interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

注意,此时在使用泛型接口的时候,需要定义泛型的类型。

装饰器·

是一个方法,可以注入到类、方法、属性上等进行扩展。 本质上接受一些相关参数,对此参数做出操作来影响对应的类或方法等

装饰器分类

  • 装饰器工厂
//装饰器工厂(可以传参)
function Greeter(greeting: string) {
  return function (target: Function) {
    target.prototype.greet = function (): void {
      console.log(greeting);
    };
  };
}
//声明装饰器,如此可在下方类扩展了Greter的方法和属性
@Greeter'hello'class Greeting {
  constructor() {
    // 内部实现
  }
}
  • 类装饰器

它接收一个参数: target: TFunction - 被装饰的类

//类装饰器,无法传参
function Greeter(target:Function) {
    target.prototype.greet = function (): void {
      console.log(greeting);
    };
}

//声明装饰器,如此可在下方类扩展了Greter的方法和属性
@Greeter
class Greeting {
  constructor() {
    // 内部实现
  }
}

let myGreeting = new Greeting();
(myGreeting as any).greet(); // console output: 'Hello TS!';

装饰器组合

结合起来的装饰器,首先会从上至下执行所有装饰器工厂,拿到所有装饰器,然后从下至上的执行所有装饰器。

  • 属性装饰器 它接收两个参数:

target: Object - 被装饰的类 propertyKey: string | symbol - 被装饰类的属性名

function logProperty(target: any, key: string) {
  delete target[key];

  const backingField = "_" + key;

  Object.defineProperty(target, backingField, {
    writable: true,
    enumerable: true,
    configurable: true
  });

  // property getter
  const getter = function (this: any) {
    const currVal = this[backingField];
    console.log(`Get: ${key} => ${currVal}`);
    return currVal;
  };

  // property setter
  const setter = function (this: any, newVal: any) {
    console.log(`Set: ${key} => ${newVal}`);
    this[backingField] = newVal;
  };

  // Create new property with getter and setter
  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Person { 
  @logProperty
  public name: string;

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

const p1 = new Person("kylee");
p1.name = "xiaozhang";

  • 方法装饰器 它接收三个参数:

  • target: Object - 被装饰的类

  • propertyKey: string | symbol - 方法名

  • descriptor: TypePropertyDescriptor - 属性描述符

function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  let originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log("wrapped function: before invoking " + propertyKey);
    let result = originalMethod.apply(this, args);
    console.log("wrapped function: after invoking " + propertyKey);
    return result;
  };
}

class Task {
  @log
  runTask(arg: any): any {
    console.log("runTask invoked, args: " + arg);
    return "finished";
  }
}

let task = new Task();
let result = task.runTask("learn ts");
console.log("result: " + result);

  • 参数装饰器 它接收三个参数:

  • target: Object - 被装饰的类

  • propertyKey: string | symbol - 方法名

  • parameterIndex: number - 方法中参数的索引值

function Log(target: Function, key: string, parameterIndex: number) {
  let functionLogged = key || target.prototype.constructor.name;
  console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`);
}

class Greeter {
  greeting: string;
  constructor(@Log phrase: string) {
    this.greeting = phrase; 
  }
}