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

前言

使用 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 那樣,

如下圖:

ts-node.png

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,這樣就會導致通過這個函數排序的結果不能確切的推斷出具體的類型,而隻能是個聯合類型,那麼我們也隻能使用這個聯合類型裡共有的方法提示,

如下圖:

image.png

image.png

這裡簡單的調用瞭一下寫好的排序函數,返回的結果類型竟然是: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 泛型重載函數 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: