2021前端必须掌握的TS | 8月更文挑战

TypeScript之泛型篇

前端的小伙伴往往看到这样的代码是否是想吐,究其缘由是还不特别了解泛型,快来跟着铁蛋儿一块儿学习吧。html

D9ED7721-C921-4FE8-80CC-9CCBCF6A3154

学完之后就能够开开心心恰饭了!前端

nice.gif

首先来看下官网给咱们的泛型介绍web

软件工程中,咱们不只要建立一致的定义良好的API,同时也要考虑可重用性。 组件不只可以支持当前的数据类型,同时也能支持将来的数据类型,这在建立大型系统时为你提供了十分灵活的功能。数组

像C#和Java这样的语言中,可使用泛型来建立可重用的组件,一个组件能够支持多种类型的数据。 这样用户就能够以本身的数据类型来使用组件。markdown

下面是来谈一谈我理解的泛型:架构

泛型能够理解为宽泛的类型,一般用于类和函数, 完了!!!函数

20171006297207_VrDhAT.jpg

泛型像是提取了一类事物的共性特性的抽象, 好比说松树、柳树都是树。oop

可是树那不必定用泛型表达,树的表达方式在程序里有3种:学习

  1. 接口
  2. 继承
  3. 泛型

image-20210803174838782

先从你们都了解的继承开始说松树继承于树,松树同时也是木材。若是用继承表达就是上图, 你们思考一个问题? 这样的话若是再出现一个物质的类这么办?ui

咱们是否是还要继承,不管谁继承谁,无疑都增长了程序设计复杂度,也增长了继承关系的维护成本或者说耦合, 因此关系太强并非特别好。不少时候没有必要很是考虑用关系表达, 这时候就能够用接口了。

接口官网是这样描述的:

接口是对行为的抽象,而具体如何行动须要由类(classes)去实现(implement)。

TypeScript中,使用接口(Interfaces)来定义对象的类型。除了可用于对类的一部分行为进行抽象之外,也经常使用于对「对象的形状(Shape)」进行描述。

人话: 描述某一个东西的某一个特性。

好比: 用护在支付场景支付, 用户在登录的时候登录, 那这两个就不必放在用户里面写。

再好比: 松树能够生长,动植物也能够生长,可是没有必要放到一块儿写。

再来看下泛型:

泛型不只仅是描述,它是对共性的提取

class TaBle<T>{
  make() {
  }
}

const A = new TaBle<红木>()
const B = new TaBle<桃木>()
复制代码

泛型是能够定义函数能够进行计算,相比于继承关系也不是太强很弱很合理

  • 木头能够制造桌子,但不是全部的木头能够制造桌子。
  • 制造桌子()这个方法, 放到木头类中会很奇怪,由于木头不只仅能够制造桌子。
  • 同理,让木头继承于 “能够制造桌子” 这个接口也很奇怪。

奇怪的代码展现:

class 红木 implements IMakeTable{
  makeBed(){....}
}
复制代码

设计IMakeTable的目标是为了拆分描述事物不一样的方面(Aspect), 其实还有一个更专业的词汇叫 “关注点”

拆分关注点的技巧,叫作关注点的分离。好比从架构层来讲 Vue3的CompositionAPI和React hooks 都是要作关注点的分离。

若是仅仅用接口,不用泛型,那么关注点就没有作到彻底解耦。

e331220d4440436f8b7eabddc6e4a841.jpeg

泛型是一种抽象共性(本质)的变成手段,它容许将类型做为其它类型的参数(表现形式),从而分离不一样关注点的实现(做用)。

泛型基础:

// test函数是一个返回数字的函数
// 传入的number 返回的也是number
function test(arg: number): number {
  return arg
}

// 为了让test函数支持更多类型能够声明参数为any
function test(arg: any): any {
  return arg
}

// 就算传进去Array返回的testVal值也不是any(implicit any)
let testVal = test(Array)

// any会丢失后续全部的检查,所以能够考虑用泛型
// <>叫作钻石操做符,表明传入的参数
function test<Type>(arg: Type): Type {
  // Type声明、传入、返回必须都是string类型
  return arg
}

// let output = test<string>('string')
// 或者直接调用 也会推导出来
let output = test('string')

// 全部给output赋其它类型的值会报错
output = 100 // error
复制代码

泛型类:

class AddNumber<NumType>{
  initValue: NumType;
  add: (x: NumType, y: NumType) => NumType
}
let myAddNumber = new AddNumber<number>()
myAddNumber.initValue = 1
// (number,number)=>number
myAddNumber.add = (x, y) => {
  return x + y
}

let myAddString = new AddNumber<string>()
myAddString.initValue = '1'
// (string,string)=>string
myAddString.add = (x, y) => {
  return x + y
}
复制代码

官网这么说:

类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,因此类的静态属性不能使用这个泛型类型。

注意: 不能这么干

src=http___5b0988e595225.cdn.sohucs.com_images_20190116_c0e43c51325e434ba4a972b08dbe4571.jpeg&refer=http___5b0988e595225.cdn.sohucs.jpeg

class AddNumber<T>{
  private initValue: T
  constructor(v: T) {
    this.initValue = v
  };
  public add(x: T, y: T) {
    return x + y
  }
}
复制代码

由于是泛型变量问题:

// test例子
function test<T>(arg: T): T {
  return arg;
}

// 获取参数数组lengt
function test<T>(arg: T): T {
  console.log(arg.length);  // 报错
  return arg;
}

function testArray<T>(arg: T[]): T[] {
  console.log(arg.length);  // 这样能够
  return arg;
}

// 或者

function testArray<T>(arg: Array<T>): Array<T> {
  console.log(arg.length);  // 这样也行
  return arg;
}
复制代码

​ 使用泛型建立像test这样的泛型函数时,编译器要求你在函数体必须正确的使用这个通用的类型。 换句话说,你必须把这些参数当作是任意或全部类型。

泛型约束

​ 有时候想操做某类型的一组值,而且咱们知道这组值具备什么样的属性。就像上例,想访问arg的length属性,可是不能保证每种类型都有length属性。 因此咱们最好对T类型作出约束, 约束的话咱们就用到了接口。

interface LengthTest {
  length: number;
}

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

// 可是 T 被约束为有length属性就再也不适合任意类型
test(1) // no

// 须要传进去符合约束的类型
test({ length: 2, value: 1 }) // yes
复制代码

操做小技巧 Keyof

keyof是索引类型查询操做符f与Object.keys略有类似,只是 keyof 是取 interface 的键,并且 keyof 取到键后会保存为联合类型。

假如咱们有这样一个需求:

实现一个getValue函数获取对像的value值?

function getValue(o: object, key: string) {
  return o[key];
}
const obj = { name: '铁蛋儿', age: 18 };
const val = getValue(obj, 'name');
复制代码

这样写有两点很差:

  1. 没法肯定返回值的类型
  2. 没法对key进行约束, 可能拼写错误

使用keyof来加强getValue函数:

// 此时key是name|age
function getValue<T extends Object, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
 
const obj = { name: '张三', age: 18 };
const val = getValue(obj, 'name');
复制代码

最后来一个官网的高级操做(类型参数实例化):

class Name {
  name: string;
}
class Job {
  job: boolean;

}
class User {
  age: number;
}
class ZhangSan extends User {
  zname: Name;
}
class LiSi extends User {
  ljob: Job;
}

function createInstance<U extends User>(o: new () => U): U {
  return new o();
}

createInstance(ZhangSan).zname.name;
createInstance(LiSi).ljob.job;
复制代码

欢迎关注B站: 前端铁蛋儿

最后给还不是很熟练的小伙伴留两个问题:

  1. 何时用接口?何时用泛型?
  2. 将类型做为参数传递, 并实例化有哪些应用场景?
相关文章
相关标签/搜索