首頁 > 軟體

typeScript 泛型使用和泛型介面結合

2022-02-22 19:01:08

typeScript 中新增的泛型概念、泛型使用、泛型與介面結合:

在實際應用中可能會遇到求最小值的問題,比如求陣列中的最小值。

ts 中的就需要寫兩種方式,一種針對 number,另外一種針對字串。

這樣寫不利於程式碼重用,專案較大時,效能較差,同時工作效率也低,所以在 ts 中引入了泛型概念。

function getMin1(arr:number[]):number {
 let min = arr[0]
 for (var i = 1; i < arr.length; i++){
  if (min > arr[i]) {
   min = arr[i]
  }
 }
 return min
}
console.log(getMin1([1, 2, 3, 4]));
function getMin2(arr:string[]):string {
 let min = arr[0]
 for (var i = 1; i < arr.length; i++){
  if (min > arr[i]) {
   min = arr[i]
  }
 }
 return min
}
console.log(getMin2(['a', 'b', 'c']));

1、泛型是啥?

泛型英文是 generics ,是指在定義函數、介面或類的時候,不預先指定具體的型別,而是在使用的時候再指定型別的一種。

定義方式:

function fnName<T>(arg:T,...):T{
 return ...
}

泛型變數通常用 T 來表示,T可以表示任何型別。

所以呢,我們可以將上述範例修改成以下程式碼:

function getMin<T>(arr: T[]): T{
 let min = arr[0]
 for (var i = 1; i < arr.length; i++){
  if (min > arr[i]) {
   min = arr[i]
  }
 }
 return min
}
getMin<number>([1, 2, 3, 4])
getMin<string>(['a', 'b', 'c', 'd'])

上述程式碼中,T 的主要作用就是幫助我們來捕獲使用者傳入的型別,比如 :numberstring 。另外編譯器也會根據傳入的引數自動地幫助我們進行型別推斷,然後把 T 設定為它的型別,所以可以忽略型別的傳入,如:

getMin([1, 2, 3, 4])
getMin(['a', 'b', 'c', 'd'])

在一些複雜的情況下,為了防止編譯器自動推斷類似失敗,儘可能地將型別傳入,防止出錯。

2、泛型型別

泛型函數的型別和非泛型函數的型別有什麼不同?

它們看著很相似,泛型函數型別前面有一個型別引數 <T>。

對於泛型函數型別還有以下特性:

a>、泛型函數型別可以有多個引數

function fn<T, U>(arg1: T, arg2: U) {
 return arg1
}

b>、泛型函數可以使用不同的泛型引數名

function fn<T>(arg1: T) {
 return arg1
}
let Fn: <M>(arg1: M) => M = fn

c>、可以使用帶有物件字面量的方式定義泛型函數

function fn<T>(arg1: T) {
 return arg1
}
let Fn: {<T>(arg: T): T} = fn

3、泛型介面

在使用物件字面量的方式定義泛型函數時,物件的形式可以替換成介面的形式,改為:

let Fn: { <T>(arg: T): T } = fn

//替換為

interface FnInter{
 <T>(arg: T): T
}
let Fn: FnInter = fn

這種方式存在問題:函數對資料型別一無所知,無法使用某個資料型別進行操作。所以需要改良下,將型別作為引數傳入,如:

interface FnInter<T>{
 (arg:T): T
}
let Fn: FnInter<string> = fn

這樣我們就能清楚地知道使用的具體是那個泛型型別。

我們將整個介面當做泛型引數,就叫做泛型介面。它的優點就是我們能清除知道引數的資料型別,介面內的成員也能知道引數的具體型別。

4、泛型類

除了有泛型介面之外,還有介面類。泛型類與泛型函數差不多。

語法格式為:

class 名稱<T>{}
new 類名<型別>()
class GetMin<T>{
  arr: T[] = []
  add(ele:T) {
 this.arr.push(ele)
  }
  getMin(): T{
 var min = this.arr[0]
 for (var i = 0; i < this.arr.length; i++){
  if (min > this.arr[i]) {
   min = this.arr[i]
  }
 }
 return min
 }
}
let gMin1 = new GetMin<number>()
gMin1.add(1)
gMin1.add(5)
console.log(gMin1.getMin());//1

let gMin2 = new GetMin<string>()
gMin2.add('a')
gMin2.add('b')
console.log(gMin2.getMin());//2

5、泛型約束

泛型功能確實挺強大的,但它也不是萬能的。比如:

function getLength<T>(val: T): number {
 return val.length;
}

錯誤資訊提示:型別“T”上不存在屬性“length”。

原因是隻有字串、陣列才有 length 屬性,對於數位、物件沒有 length 屬性,所以報錯了。解決辦法是要保證傳入的資料型別有 length 屬性,所以需要使用泛型約束。

泛型約束主要是通過介面 + extends 關鍵字來實現約束。

interface ILen{
 length:number
}
function getLength<T extends ILen>(val: T): number {
 return val.length;
}
console.log(getLength<string>("abcd"));
console.log(getLength<number>(1)); //錯誤提示:型別「number」不滿足約束「ILen」。

使用泛型約束的優點是幫我們自動檢測傳入的值是否符合約束型別的值,不滿足時就會有錯誤提示。

6、泛型引數預設型別

在 typeScript 2.3 以後,可以為泛型中的型別引數指定預設型別,當使用泛型時沒有指定引數型別,並且編輯器從實際引數中也無法推斷出資料型別時,就使用預設型別。

使用簡單:

interface P<T = string>{
 name:T
}
let p1: P = { name: "小姐姐" }
let p2: P<number> = { name: 18 }

​泛型引數的預設型別遵循以下規則:​

  • 有預設型別的型別引數被認為是可選的。
  • 必選的型別引數不能在可選的型別引數後。
  • 如果型別引數有約束,型別引數的預設型別必須滿足這個約束。
  • 當指定型別實參時,你只需要指定必選型別引數的型別實參。 未指定的型別引數會被解析為它們的預設型別。
  • 如果指定了預設型別,且型別推斷無法選擇一個候選型別,那麼將使用預設型別作為推斷結果。
  • 一個被現有類或介面合併的類或者介面的宣告可以為現有型別引數引入預設型別。
  • 一個被現有類或介面合併的類或者介面的宣告可以引入新的型別引數,只要它指定了預設型別。

7、泛型條件型別

在 typeScript 2.8 中,引入了條件型別,我們可以根據某些條件得到不同的型別,此處的條件是型別相容性約束。ript 2.8 中,引入了條件型別,我們可以根據某些條件得到不同的型別,此處的條件是型別相容性約束。

條件型別會以一定條件表示式進行型別關係檢測,從而在兩種型別中選擇其一:

使用語法:

T extends U ? X : Y

​以上表示式的意思是:若 ​T​ 能夠賦值給 ​U​,那麼型別是 ​X​,否則為 ​Y​。

到此這篇關於typeScript 泛型使用和泛型介面結合的文章就介紹到這了,更多相關typeScript 泛型內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com