TypeScript 学习总结 (三)

TypeScript 学习总结 (三)

前言:TypeScript 内容,会分 3 篇文章来写,可以持续关注哟~

本章主要内容

  • 1,ts 类
  • 2,ts 泛型
  • 3,ts 装饰器

1,ts 类

1.1,类的属性与方法

在面向对象中,类是一种面向对象计算机编程语言的构造,在 ts 中,我们可以通过 class 来定义一个类

class Person {
  // 静态属性
  static name: string = "xiaowang"  // es6的静态方法前如果加 static 关键字,表示该方法不会被实例继承,而是直接通过类来调用
  // 成员属性
  greeting: string
  // 构造函数 - 执行初始化操作
  constructor(message: string) {
    this.greeting = message
  }
  // 静态方法
  static getClassName() {
    return "my name is xiaowang"
  }
  // 成员方法
  greet() {
    return "Hello, " + this.greeting
  }
}
let p1 = new Person("world")

以上代码编译成 ES5为:

var Person = /** @class */ (function () {
  // 构造函数 - 执行初始化操作
  function Person(message) {
      this.greeting = message
  }
  // 静态方法
  Person.getClassName = function () {
      return "my name is xiaowang"
  }
  // 成员方法
  Person.prototype.greet = function () {
      return "Hello, " + this.greeting
  };
  // 静态属性
  Person.name = "xiaowang"
  return Person
}())
var p1 = new Person("world")
1.2, ECMAScript 私有字段
class Person {
  #name: string  // 私有变量以 # 开头,我们成为私有名称
  constructor(name: string) {
    this.#name = name
  }
  greet() {
    console.log(`Hello, my name is ${this.#name}`)
  }
}

let p1 = new Person("xiaowang")

不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private),私有字段不能在包含的类之外访问,甚至不能被检测到。

1.3,访问器

在 ts 中,我们可以通过 getter 和 setter 方法来实现数据的封装和有效性校验,防止出现异常数据

let str = "string"
class Person {
  private name: string
  get fullName(): string {
    return this.name
  }
  set fullName(newName: string) {
    if (str && str == "string") {
      this.name = newName
    } else {
      console.log(1)
    }
  }
}
let p1 = new Person()
p1.fullName = "xiaowang"
console.log(p1)
console.log(p1.fullName)
image
1.4,类的继承

继承是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,可以增加它自己的新功能的能力, ts 中可以通过 extends 关键字实现继承

class Parent {
  name: string
  constructor(name: string) {
    this.name = 'parent'
    this.age = 30
  }

  getName() {
    return `my name is ${this.name}`
  }
}

class Son extends Parent {
  constructor() {
    super()
  }
  getAge() {
    return `age: ${this.age}`
  }
}
let s = new Son()
console.log(s.getName())
console.log(s.getAge())
image
1.5,抽象类

抽象类: 是使用 abstract 关键字声明的类,抽象类不能被实例化,因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法

abstract class Person {
  constructor(public name: string){}

  abstract getName() {}
}

const p1 = new Person();  // error TS2511: Cannot create an instance of an abstract class.

抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类:

abstract class Person {
  constructor(public name: string){}

  // 抽象方法
  abstract say(words: string) :void;
}
class Son extends Person {
  constructor(name: string) {
    super(name);
  }
  say(words: string): void {
    console.log(`${this.name} say ${words}`);
  }
}
const s = new Son("xiaowang")
s.say("Hello")  // xiaowang say Hello
1.6,类方法重载

重载:一个类中可以有多个方法,方法名相同,方法的参数和类型不同

class Person {
  getName(): void
  getName(name: string): void
  getName(name?: string) {
    if(name === 'xiaowang') {
        console.log(`我是 ${name}`)
    } else {
        console.log(`我们不是 ${name}`)
    }  
  }
}
const p1 = new Person()
p1.getName('xiaowang')
p1.getName()
image

1,ts 泛型

泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型

设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值

2.1,泛型语法

像传递参数一样,可以传递了我们想要用于特定函数调用的类型

function getData  (value: T): T { 
  return value
}
//  是传递类型, 这里的 T 称为 类型变量,它是我们传递给 getData 函数的类型占位符,同时它被分配给 value 参数用来代替它的类型:此时 T 充当的是类型,而不是特定的 Number 类型
// value: T 是链式传递给参数类型
// :T 是返回类型

以上代码,我们使用 getData (1)调用时,Number类型就像参数 1 一样,它将在出现 T 的任何位置填充该类型。

其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思

  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型

其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 getData 函数:

function getData  (value: T, message: U): T { 
  console.log(message)
  return value
}
console.log(getData(100, 'xiaowang'))
image

除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

function getData  (value: T, message: U): T { 
  console.log(message)
  return value
}
console.log(getData(100, 'xiaowang'))

以上代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 T 和 U,而不需要我们显式指定它们

2.2,泛型接口
interface getData {
  (arg: T): T
}
2.3,泛型类
class GenericNumber {
  zeroValue: T
  add: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function (x, y) {
  return x + y
}
2.4, 泛型工具类型

ts 常见的工具类型,如 Partial、Required、Readonly、Record 和 ReturnType 等

2.4.1, typeof

在 ts 中,typeof 操作符可以用来获取一个变量声明或对象的类型

interface Person {
  name: string
  age: number
}
const p1: Person = { name: 'xiaowang', age: 18 }
type person1 = typeof p1  // object -> Person

function toArray(x: number): Array {
  return [x]
}
type Func = typeof toArray // function
2.4.2, keyof

keyof 操作符用于获取某种类型的所有键,其返回类型是联合类型

interface Person {
  name: string;
  age: number;
}
type p1 = keyof Person; // "name" | "age"
type p2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type p3 = keyof { [x: string]: Person };  // string | number

在 ts 中支持两种索引签名,数字索引和字符串索引:

interface StringArray {
  // 字符串索引 -> keyof StringArray => string | number
  [index: string]: string; 
}

interface StringArray1 {
  // 数字索引 -> keyof StringArray1 => number
  [index: number]: string;
}

为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,Js 在执行索引操作时,会先把数值索引先转换为字符串索引。所以 keyof { [x: string]: Person } 的结果会返回 string | number

2.4.3, in

用来遍历枚举类型:

type str = "a" | "b" | "c"

type Obj =  {
  [p in str]: any
} // -> { a: any, b: any, c: any }
2.4.4, infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用

type ReturnType = T extends (
  ...args: any[]
) => infer R ? R : any

以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用

2.4.5, extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束

interface Lengthwise {
  length: number
}

function loggingIdentity(arg: T): T {
  console.log(arg.length)
  return arg
}

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

loggingIdentity(3);  // Error, number doesn't have a .length property

这时我们需要传入符合约束类型的值,必须包含必须的属性:

loggingIdentity({length: 10, value: 3});
2.4.6, Partial

Partial 的作用就是将某个类型里的属性全部变为可选项 ?

定义:

type Partial = {
  [P in keyof T]?: T[P]
}

在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "Learn TS",
  description: "Learn TypeScript",
};

const todo2 = updateTodo(todo1, {
  description: "Learn TypeScript Enum",
})

在上面的 updateTodo 方法中,我们利用 Partial 工具类型,定义 fieldsToUpdate 的类型为 Partial,即:

{
 title?: string | undefined;
 description?: string | undefined;
}

3, 装饰器

装饰器是一个表达式,该表达式被执行后,返回一个函数,函数的入参分贝是target、name和descriptor,执行函数后,可能返回 descriptor 对象,用于配置 target 对象

装饰器 种类:

  • 类装饰器
  • 属性装饰器
  • 方法装饰器
  • 参数装饰器

需要注意的是,若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:

// 命令
tsc --target ES5 --experimentalDecorators

// tsconfig.json:
{
  "compilerOptions": {
     "target": "ES5",
     "experimentalDecorators": true
   }
}
3.1,类装饰器

类装饰器声明:

declare type ClassDecorator = (target: TFunction) => TFunction | void

// 类装饰器顾名思义,就是用来装饰类的。它接收一个参数:
// target: TFunction - 被装饰的类
function Greeter(target: Function): void {
  target.prototype.greet = function (): void {
    console.log("Hello xiaowang")
  }
}

@Greeter
class Greeting {
  constructor() {
  }
}

let greet = new Greeting()
(greet as any).greet() // console output: 'Hello xiaowang'

以上代码定义了 Greeter 类装饰器,同时我们使用了 @Greeter 语法糖,来使用装饰器

function Greeter(greeting: string) {
  return function (target: Function) {
    target.prototype.greet = function (): void {
      console.log(greeting)
    }
  }
}

@Greeter("Hello Jerry")
class Greeting {
  constructor() {
  }
}
let greet = new Greeting()
(greet as any).greet(); // console output: 'Hello Jerry'
3.2,属性装饰器

属性装饰器声明:

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("xiaowang")
p1.name = "Jerry"

以上代码我们定义了一个 logProperty 函数,来跟踪用户对属性的操作,当代码成功运行后,在控制台会输出以下结果

Set: name => xiaowang
Set: name => Jerry
3.3,方法装饰器

方法装饰器声明:

declare type MethodDecorator = (target:Object, propertyKey: string | symbol,         
  descriptor: TypePropertyDescript) => TypedPropertyDescriptor | void
  
// 方法装饰器顾名思义,用来装饰类的方法。它接收三个参数:
// target: Object - 被装饰的类
// propertyKey: string | symbol - 方法名
// descriptor: TypePropertyDescript - 属性描述符
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)

以上代码成功运行后,控制台会输出以下结果:

"wrapped function: before invoking runTask" 
"runTask invoked, args: learn ts" 
"wrapped function: after invoking runTask" 
"result: finished"
3.4,参数装饰器

参数装饰器声明:

declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, 
  parameterIndex: number ) => void
// 参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:
// 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
  }
}

以上代码输出:

"The parameter in position 0 at Greeter has been decorated" 

你可能感兴趣的:(TypeScript 学习总结 (三))