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. 將類型做爲參數傳遞, 並實例化有哪些應用場景?
相關文章
相關標籤/搜索