首頁 > 軟體

TypeScript 泛型過載函數的使用方式

2022-08-02 14:02:30

前言

使用 TypeScript 進行開發也已經有段日子了,雖然最開始接觸後以為這不就和 Java 一樣一樣的麼,然而越深入瞭解越發現還真不一樣~不過有些概念來說是相通的,比如泛型。 Java 裡也有函數過載,但是和 TS 差別還是挺大的,那就通過一個排序功能來了解一下 TS 中的泛型過載吧

TypeScript 的執行環境

1. ts-node

ts-node 是 typescript 的 node.js 解釋和執行器,也就是說,它可以像 node 執行 JavaScript 一樣執行 typescript 程式碼。

使用方式也很簡單,在專案中安裝 typescript 和 ts-node 的依賴後使用 ts-node 命令執行 ts 檔案即可。

1.在命令列安裝依賴

npm install typescript ts-node

2.使用 ts-node 執行 ts 檔案(這裡使用 npx 來執行 ts-node,因為 ts-node 是在本地專案中安裝的,如果直接使用的話會報命令找不到的錯誤,當然如果在全域性安裝了 ts-node npm 包就直接可以使用 ts-node 來執行,如果只是本地安裝了,那麼需要加上 npx,這是會到 node_modules 資料夾中找到 ts-node 包來進行執行。)

全域性安裝了: ts-node: ts-node xxx.ts
本地專案安裝了 ts-node: npx ts-node xxx.ts

補充: ts-node 也可以直接在命令列中編寫和執行 ts 程式碼,像 python 那樣,

如下圖:

2. tsc

tsc 顧名思義就是 typescript 的編譯器名稱,在安裝完 typescript 後,即可以使用 tsc 命令執行 ts 檔案,當然不同於 ts-node 能夠直接執行 ts 檔案,tsc 只是將 ts 檔案編譯後輸出成 js 檔案,然後可以再通過 node 來執行生成的 js 檔案。

tsc 有許多的設定項,當執行 tsc --init 時可以在專案中生成 tsconfig.json 檔案,這個檔案裡面包含了許多的設定,包括設定編譯後的檔案輸出路徑,編譯後的檔案用哪種模組規範以及相容 es6 語法等等選項。

//1.安裝 typescript
npm install typescript

//2.使用 tsc 生成 tsconfig.json 檔案
npx tsc --init

//3.使用 tsc 編譯 ts 檔案
npx tsc xxx.ts

TypeScript 中的函數過載

TS 中的函數過載並不像其它語言中的函數過載一樣,和其它語言如 Java 比起來,更像是一種偽過載,它不能像 Java 中過載那樣實現同樣的函數名,但是引數個數不一樣,而是更多的為型別推斷服務。

簡單的排序演演算法

首先,使用 TS 來實現一個快速排序函數:

1. 快速排序

function quickSort<T>(arr: Array<T>): T[] {
    if (arr.length < 2) return arr

    const left: Array<T> = []
    const right: Array<T> = []

    const mid = arr.splice(Math.floor(arr.length / 2), 1)[0]
    for (let item of arr) {
        if (item < mid) {
            left.push(item)
        } else {
            right.push(item)
        }
    }
    return quickSort(left).concat(mid, quickSort(right))
}

上面這段程式碼是使用泛型實現的快速排序函數,快速排序比氣泡排序的效能要好很多,基本思想就是分治(divide and conquer),簡單來說就是先選一個元素作為中間數,然後分成兩部分,小於這個元素的部分,和大於這個元素部分,接著再使用遞迴分別進行處理這兩部分,將排序任務分解到最小,然後再合併。

上面程式碼中的快速排序方式,如果傳遞的是英文陣列那就沒問題,但是如果傳遞的是中文陣列,那就不能正常排序了,所以中文陣列需要單獨進行處理,使用下面的函數:

2. 中文排序

// 通過正規表示式,判斷是否是中文陣列
function isChinese<T>(arr: Array<T>): boolean {
    const pattern = /[u4e00-u9fa5]+/g;
    return arr.some((item: any) => {
        return pattern.test(item)
    })
}

// 中文排序
function chineseSort<T>(arr: Array<T>): T[] {
    return arr.sort((first, second) => {
        return (first as any).localeCompare(second, 'zh-CN')
    })
}

如果是中文陣列,那麼使用陣列內建的 sort 函數進行排序。

接下來,如果需要將英文陣列中的每一項進行排序,則還需要單獨的函數進行處理:

3. 字串自排序

// 英文自排序
function strSelfSort(str: string): string {
    const strArr = str.split('')
    return quickSort(strArr).join('')
}

實現英文字串自排序就是先將字串進行 split 分割成字元陣列,然後傳遞到之前寫的快速排序函數中,得到結果後再通過 join 函數拼接成字串返回。

那麼,接下來將上面的幾種排序功能整合成一個單獨的通用函數:

4. 通過泛型整合幾種排序

// 通用的排序函數
function sort<T>(data: T): T[] | string {
    if (typeof data === "string") {
        return strSelfSort(data)
    }
    if (data instanceof Array) {
        if (isChinese(data)) {
            return chineseSort(data)
        }
        const newArr = data.map((item) => {
            return typeof item === "string" ? strSelfSort(item) : item
        })
        return quickSort(newArr)
    }
    throw new Error(`data must be string or array. ${data}`)
}

通過上面的通用排序函數可以看出,在函數內部通過排序傳遞的資料型別,如果是 string 則呼叫自排序,接著如果是陣列的話,再判斷是否是中文陣列,如果是,則呼叫中文陣列排序函數進行排序,不是的話就認定為是英文陣列,英文陣列首先使用 map 函數遍歷,判斷陣列中如果存在 string 型別就先呼叫字串自排序函數進行排序,否則就原樣返回,最後再通過快速排序函數進行排序,這樣就完成了英文陣列既每一項都排序了,整體陣列也進行了排序。

雖然上面的排序函數功能已經比較完善了,但是有一點不太好的地方就是這個函數返回了一個聯合型別 T[] | string,這樣就會導致通過這個函數排序的結果不能確切的推斷出具體的型別,而只能是個聯合型別,那麼我們也只能使用這個聯合型別裡共有的方法提示,

如下圖:

這裡簡單的呼叫了一下寫好的排序函數,返回的結果型別竟然是:string | string[][] 的聯合型別,這樣的返回結果對於開發後續功能來說很不友好,那麼接下來,就使用 TS 中的函數過載來完善一下上面的排序函數:

5. 使用函數過載完善排序功能

// 使用函數過載重構排序函數
function sort(data: string): string
function sort<T>(data: T): T
function sort(data: any): any {
    if (typeof data === "string") {
        return strSelfSort(data)
    }
    if (data instanceof Array) {
        if (isChinese(data)) {
            return chineseSort(data)
        }
        const newArr = data.map((item) => {
            return typeof item === "string" ? strSelfSort(item) : item
        })
        return quickSort(newArr)
    }
    throw new Error(`data must be string or array. ${data}`)
}

關於 TS 的函數過載,就是隻有一個實現的函數,其餘都是函數簽名,而且必須放在實現函數的上面,在呼叫這個函數的時候,只會顯示上面的函數簽名函數,而不會展示具體實現的函數,但是實際執行的卻是那個實現函數,在這種情況下,通過使用函數過載寫個兩個不同引數和返回值的函數簽名提供給呼叫者使用,而在具體實現函數中去相容處理,這樣做的好處就是呼叫者得到的返回值型別可以是某個具體的型別了,而不再是個聯合型別,更有益於後面的開發。

總結

通過使用 TS 的函數過載解決了當一個函數返回一個聯合型別時,型別推斷不確定的問題,在某些會返回聯合型別的場景下可以嘗試使用,方便後續的型別推斷操作,所以說,TS 的一切真的都是為型別而服務的,怎麼寫好 TS 程式碼其實就是在更好的完善型別推斷,型別系統的過程,只有更好更準確的型別推斷,才能發揮 TS 的作用,讓編譯器在開發過程中智慧的告訴開發者有哪些屬性和方法可以呼叫,並且在呼叫了錯誤的屬性和方法後可以及時提醒開發者。

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


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