TypeScript
- TypeScript基礎知識
-
- 1. 基礎類型
-
- 布爾值
- 數字
- 字元串
- undefined 和 null
- 數組
- 元組 Tuple
- 枚舉
- any
- void
- object
- 聯合類型
- 類型斷言
- 類型推斷
- 2. 接口
-
- 接口初探
- 可選屬性
- 隻讀屬性
- readonly vs const
- 函數類型
- 類類型
-
- 類實作接口
- 一個類可以實作多個接口
- 接口繼承接口
- 3. 類
-
- 基本示例
- 繼承
- 公共,私有與受保護的修飾符
- 預設為 public
- 了解 private
- 了解 protected
- readonly 修飾符
- 參數屬性
- 存取器
- 靜态屬性
- 抽象類
- 4. 函數
-
- 基本示例
- 函數類型
- 為函數定義類型
- 書寫完整函數類型
- 可選參數和預設參數
- 剩餘參數
- 函數重載
- 5. 泛型
-
- 引入
- 使用函數泛型
- 多個泛型參數的函數
- 泛型接口
- 泛型類
- 泛型限制
TypeScript基礎知識
1. 基礎類型
TypeScript 支援與 JavaScript 幾乎相同的資料類型,此外還提供了實用的枚舉類型友善我們使用。
布爾值
最基本的資料類型就是簡單的 true/false 值,在JavaScript 和 TypeScript 裡叫做
boolean
(其它語言中也一樣)。
let isDone: boolean = false;
isDone = true;
// isDone = 2 // error
數字
和 JavaScript 一樣,TypeScript 裡的所有數字都是浮點數。 這些浮點數的類型是 number。 除了支援十進制和十六進制字面量,TypeScript 還支援 ECMAScript 2015中引入的二進制和八進制字面量。
let a1: number = 10 // 十進制
let a2: number = 0b1010 // 二進制
let a3: number = 0o12 // 八進制
let a4: number = 0xa // 十六進制
字元串
JavaScript 程式的另一項基本操作是處理網頁或伺服器端的文本資料。 像其它語言裡一樣,我們使用
string
表示文本資料類型。 和 JavaScript 一樣,可以使用雙引号(
"
)或單引号(
'
)表示字元串。
let name:string = 'tom'
name = 'jack'
// name = 12 // error
let age:number = 12
const info = `My name is ${name}, I am ${age} years old!`
undefined 和 null
TypeScript 裡,
undefined
和
null
兩者各自有自己的類型分别叫做
undefined
和
null
。 它們的本身的類型用處不是很大:
let u: undefined = undefined
let n: null = null
預設情況下
null
和
undefined
是所有類型的子類型。 就是說你可以把
null
和
undefined
指派給
number
類型的變量。
數組
TypeScript 像 JavaScript 一樣可以操作數組元素。 有兩種方式可以定義數組。 第一種,可以在
元素類型後面接上[]
,表示由此類型元素組成的一個數組:
第二種方式是使用數組泛型,
Array<元素類型>
:
元組 Tuple
元組類型允許表示一個已知元素數量和類型的數組,
各元素的類型不必相同
。 比如,你可以定義一對值分别為
string
和
number
類型的元組。
let t1: [string, number]
t1 = ['hello', 10] // OK
t1 = [10, 'hello'] // Error
當通路一個已知索引的元素,會得到正确的類型:
console.log(t1[0].substring(1)) // OK
console.log(t1[1].substring(1)) // Error, 'number' 不存在 'substring' 方法
枚舉
enum
類型是對 JavaScript 标準資料類型的一個補充。 使用枚舉類型可以
為一組數值賦予友好的名字
。
預設情況下,從
開始為元素編号。 你也可以手動的指定成員的數值。 例如,我們将上面的例子改成從
1
開始編号:
enum Color {Red = 1, Green, Blue}let c: Color = Color.Green
或者,全部都采用手動指派:
enum Color {Red = 1, Green = 2, Blue = 4}let c: Color = Color.Green
枚舉類型提供的一個便利是你可以由枚舉的值得到它的名字。 例如,我們知道數值為 2,但是不确定它映射到 Color 裡的哪個名字,我們可以查找相應的名字:
any
有時候,我們會想要為那些在程式設計階段還不清楚類型的變量指定一個類型。 這些值可能來自于動态的内容,比如來自使用者輸入或第三方代碼庫。 這種情況下,我們不希望類型檢查器對這些值進行檢查而是直接讓它們通過編譯階段的檢查。 那麼我們可以使用
any
類型來标記這些變量:
在對現有代碼進行改寫的時候,
any
類型是十分有用的,它允許你在編譯時可選擇地包含或移除類型檢查。并且當你隻知道一部分資料的類型時,
any
類型也是有用的。 比如,你有一個數組,它包含了不同的類型的資料:
void
某種程度上來說,
void
類型像是與
any
類型相反,它
表示沒有任何類型
。 當一個函數沒有傳回值時,你通常會見到其傳回值類型是
void
:
/* 表示沒有任何類型, 一般用來說明函數的傳回值不能是undefined和null之外的值 */
function fn(): void {
console.log('fn()')
// return undefined
// return null
// return 1 // error
}
聲明一個
void
類型的變量沒有什麼大用,因為你隻能為它賦予
undefined
和
null
:
object
object
表示非原始類型,也就是除
number
,
string
,
boolean
之外的類型。
使用
object
類型,就可以更好的表示像
Object.create
這樣的
API
。例如:
function fn2(obj:object):object {
console.log('fn2()', obj)
return {}
// return undefined
// return null
}
console.log(fn2(new String('abc')))
// console.log(fn2('abc') // error
console.log(fn2(String))
聯合類型
聯合類型(Union Types)表示取值可以為多種類型中的一種
需求1: 定義一個一個函數得到一個數字或字元串值的字元串形式值
function toString2(x: number | string) : string {
return x.toString()
}
需求2: 定義一個一個函數得到一個數字或字元串值的長度
function getLength(x: number | string) {
// return x.length // error
if (x.length) { // error
return x.length
} else {
return x.toString().length
}
}
類型斷言
通過類型斷言這種方式可以告訴編譯器,“相信我,我知道自己在幹什麼”。 類型斷言好比其它語言裡的類型轉換,但是不進行特殊的資料檢查和解構。 它沒有運作時的影響,隻是在編譯階段起作用。 TypeScript 會假設你,程式員,已經進行了必須的檢查。
類型斷言有兩種形式。 其一是“尖括号”文法, 另一個為
as
文法
/*
類型斷言(Type Assertion): 可以用來手動指定一個值的類型
文法:
方式一: <類型>值
方式二: 值 as 類型 tsx中隻能用這種方式
*/
/* 需求: 定義一個函數得到一個字元串或者數值資料的長度 */
function getLength(x: number | string) {
if ((<string>x).length) {
return (x as string).length
} else {
return x.toString().length
}
}
console.log(getLength('abcd'), getLength(1234))
類型推斷
類型推斷: TS會在沒有明确的指定類型的時候推測出一個類型
有下面2種情況: 1. 定義變量時指派了, 推斷為對應的類型. 2. 定義變量時沒有指派, 推斷為any類型
/* 定義變量時指派了, 推斷為對應的類型 */
let b9 = 123 // number
// b9 = 'abc' // error
/* 定義變量時沒有指派, 推斷為any類型 */
let b10 // any類型
b10 = 123
b10 = 'abc'
2. 接口
TypeScript 的核心原則之一是對值所具有的結構進行類型檢查。我們使用接口(Interfaces)來定義對象的類型。
接口是對象的狀态(屬性)和行為(方法)的抽象(描述)
接口初探
需求: 建立人的對象, 需要對人的屬性進行一定的限制
id是number類型, 必須有, 隻讀的
name是string類型, 必須有
age是number類型, 必須有
sex是string類型, 可以沒有
下面通過一個簡單示例來觀察接口是如何工作的:
/*
在 TypeScript 中,我們使用接口(Interfaces)來定義對象的類型
接口: 是對象的狀态(屬性)和行為(方法)的抽象(描述)
接口類型的對象
多了或者少了屬性是不允許的
可選屬性: ?
隻讀屬性: readonly
*/
/*
需求: 建立人的對象, 需要對人的屬性進行一定的限制
id是number類型, 必須有, 隻讀的
name是string類型, 必須有
age是number類型, 必須有
sex是string類型, 可以沒有
*/
// 定義人的接口
interface IPerson {
id: number
name: string
age: number
sex: string
}
const person1: IPerson = {
id: 1,
name: 'tom',
age: 20,
sex: '男'
}
類型檢查器會檢視對象内部的屬性是否與IPerson接口描述一緻, 如果不一緻就會提示類型錯誤。
可選屬性
接口裡的屬性不全都是必需的。 有些是隻在某些條件下存在,或者根本不存在。
interface IPerson {
id: number
name: string
age: number
sex?: string
}
帶有可選屬性的接口與普通的接口定義差不多,隻是在可選屬性名字定義的後面加一個
?
符号。
可選屬性的好處之一是可以對可能存在的屬性進行預定義,好處之二是可以捕獲引用了不存在的屬性時的錯誤。
const person2: IPerson = {
id: 1,
name: 'tom',
age: 20,
// sex: '男' // 可以沒有
}
隻讀屬性
一些對象屬性隻能在對象剛剛建立的時候修改其值。 你可以在屬性名前用
readonly
來指定隻讀屬性:
interface IPerson {
readonly id: number
name: string
age: number
sex?: string
}
一旦指派後再也不能被改變了。
const person2: IPerson = {
id: 2,
name: 'tom',
age: 20,
// sex: '男' // 可以沒有
// xxx: 12 // error 沒有在接口中定義, 不能有
}
person2.id = 2 // error
readonly vs const
最簡單判斷該用
readonly
還是
const
的方法是看要把它做為變量使用還是做為一個屬性。 做為變量使用的話用
const
,若做為屬性則使用
readonly
。
函數類型
接口能夠描述 JavaScript 中對象擁有的各種各樣的外形。 除了描述帶有屬性的普通對象外,接口也可以描述函數類型。
為了使用接口表示函數類型,我們需要給接口定義一個調用簽名。它就像是一個隻有參數清單和傳回值類型的函數定義。參數清單裡的每個參數都需要名字和類型。
/*
接口可以描述函數類型(參數的類型與傳回的類型)
*/
interface SearchFunc {
(source: string, subString: string): boolean
}
這樣定義後,我們可以像使用其它接口一樣使用這個函數類型的接口。 下例展示了如何建立一個函數類型的變量,并将一個同類型的函數指派給這個變量。
const mySearch: SearchFunc = function (source: string, sub: string): boolean {
return source.search(sub) > -1
}
console.log(mySearch('abcd', 'bc'))
類類型
-
類實作接口
與 C# 或 Java 裡接口的基本作用一樣,TypeScript 也能夠用它來明确的強制一個類去符合某種契約。
/*
類類型: 實作接口
1. 一個類可以實作多個接口
2. 一個接口可以繼承多個接口
*/
interface Alarm {
alert(): any;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
-
一個類可以實作多個接口
class Car2 implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
-
接口繼承接口
和類一樣,接口也可以互相繼承。 這讓我們能夠從一個接口裡複制成員到另一個接口裡,可以更靈活地将接口分割到可重用的子產品裡。
interface LightableAlarm extends Alarm, Light {
}
3. 類
對于傳統的 JavaScript 程式我們會使用
函數
和
基于原型的繼承
來建立可重用的元件,但對于熟悉使用面向對象方式的程式員使用這些文法就有些棘手,因為他們用的是
基于類的繼承
并且對象是由類建構出來的。 從 ECMAScript 2015,也就是 ES6 開始, JavaScript 程式員将能夠使用基于類的面向對象的方式。 使用 TypeScript,我們允許開發者現在就使用這些特性,并且編譯後的 JavaScript 可以在所有主流浏覽器和平台上運作,而不需要等到下個 JavaScript 版本。
基本示例
下面看一個使用類的例子:
/*
類的基本定義與使用
*/
class Greeter {
// 聲明屬性
message: string
// 構造方法
constructor (message: string) {
this.message = message
}
// 一般方法
greet (): string {
return 'Hello ' + this.message
}
}
// 建立類的執行個體
const greeter = new Greeter('world')
// 調用執行個體的方法
console.log(greeter.greet())
如果你使用過 C# 或 Java,你會對這種文法非常熟悉。 我們聲明一個
Greeter
類。這個類有 3 個成員:一個叫做
message
的屬性,一個構造函數和一個
greet
方法。
你會注意到,我們在引用任何一個類成員的時候都用了
this
。 它表示我們通路的是類的成員。
後面一行,我們使用
new
構造了
Greeter
類的一個執行個體。它會調用之前定義的構造函數,建立一個
Greeter
類型的新對象,并執行構造函數初始化它。
最後一行通過
greeter
對象調用其
greet
方法
繼承
在 TypeScript 裡,我們可以使用常用的面向對象模式。 基于類的程式設計中一種最基本的模式是允許使用繼承來擴充現有的類。
看下面的例子:
/*
類的繼承
*/
class Animal {
run (distance: number) {
console.log(`Animal run ${distance}m`)
}
}
class Dog extends Animal {
cry () {
console.log('wang! wang!')
}
}
const dog = new Dog()
dog.cry()
dog.run(100) // 可以調用從父中繼承得到的方法
這個例子展示了最基本的繼承:類從基類中繼承了屬性和方法。 這裡,
Dog
是一個 派生類,它派生自
Animal
基類,通過
extends
關鍵字。 派生類通常被稱作子類,基類通常被稱作超類。
因為
Dog
繼承了
Animal
的功能,是以我們可以建立一個
Dog
的執行個體,它能夠
cry()
和
run()
。
下面我們來看個更加複雜的例子。
class Animal {
name: string
constructor (name: string) {
this.name = name
}
run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Snake extends Animal {
constructor (name: string) {
// 調用父類型構造方法
super(name)
}
// 重寫父類型的方法
run (distance: number=5) {
console.log('sliding...')
super.run(distance)
}
}
class Horse extends Animal {
constructor (name: string) {
// 調用父類型構造方法
super(name)
}
// 重寫父類型的方法
run (distance: number=50) {
console.log('dashing...')
// 調用父類型的一般方法
super.run(distance)
}
xxx () {
console.log('xxx()')
}
}
const snake = new Snake('sn')
snake.run()
const horse = new Horse('ho')
horse.run()
// 父類型引用指向子類型的執行個體 ==> 多态
const tom: Animal = new Horse('ho22')
tom.run()
/* 如果子類型沒有擴充的方法, 可以讓子類型引用指向父類型的執行個體 */
const tom3: Snake = new Animal('tom3')
tom3.run()
/* 如果子類型有擴充的方法, 不能讓子類型引用指向父類型的執行個體 */
// const tom2: Horse = new Animal('tom2')
// tom2.run()
這個例子展示了一些上面沒有提到的特性。 這一次,我們使用
extends
關鍵字建立了 Animal的兩個子類:
Horse
和
Snake
。
與前一個例子的不同點是,派生類包含了一個構造函數,它 必須調用
super()
,它會執行基類的構造函數。 而且,在構造函數裡通路
this
的屬性之前,我們 一定要調用
super()
。 這個是 TypeScript 強制執行的一條重要規則。
這個例子示範了如何在子類裡可以重寫父類的方法。
Snake
類和
Horse
類都建立了
run
方法,它們重寫了從
Animal
繼承來的
run
方法,使得
run
方法根據不同的類而具有不同的功能。注意,即使
tom
被聲明為
Animal
類型,但因為它的值是
Horse
,調用
tom.run(34)
時,它會調用
Horse
裡重寫的方法。
sliding...
sn run 5m
dashing...
ho run 50m
公共,私有與受保護的修飾符
-
預設為 public
在上面的例子裡,我們可以自由的通路程式裡定義的成員。 如果你對其它語言中的類比較了解,就會注意到我們在之前的代碼裡并沒有使用
public
來做修飾;例如,C# 要求必須明确地使用
public
指定成員是可見的。 在 TypeScript 裡,成員都預設為
public
。
你也可以明确的将一個成員标記成
public
。 我們可以用下面的方式來重寫上面的
Animal
類:
-
了解 private
當成員被标記成
private
時,它就不能在聲明它的類的外部通路。
-
了解 protected
protected
修飾符與
private
修飾符的行為很相似,但有一點不同,
protected
成員在派生類中仍然可以通路。例如:
/*
通路修飾符: 用來描述類内部的屬性/方法的可通路性
public: 預設值, 公開的外部也可以通路
private: 隻能類内部可以通路
protected: 類内部和子類可以通路
*/
class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
public run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Person extends Animal {
private age: number = 18
protected sex: string = '男'
run (distance: number=5) {
console.log('Person jumping...')
super.run(distance)
}
}
class Student extends Person {
run (distance: number=6) {
console.log('Student jumping...')
console.log(this.sex) // 子類能看到父類中受保護的成員
// console.log(this.age) // 子類看不到父類中私有的成員
super.run(distance)
}
}
console.log(new Person('abc').name) // 公開的可見
// console.log(new Person('abc').sex) // 受保護的不可見
// console.log(new Person('abc').age) // 私有的不可見
readonly 修飾符
你可以使用
readonly
關鍵字将屬性設定為隻讀的。 隻讀屬性必須在聲明時或構造函數裡被初始化。
class Person {
readonly name: string = 'abc'
constructor(name: string) {
this.name = name
}
}
let john = new Person('John')
// john.name = 'peter' // error
參數屬性
在上面的例子中,我們必須在
Person
類裡定義一個隻讀成員
name
和一個參數為
name
的構造函數,并且立刻将
name
的值賦給
this.name
,這種情況經常會遇到。 參數屬性可以友善地讓我們在一個地方定義并初始化一個成員。 下面的例子是對之前
Person
類的修改版,使用了參數屬性:
class Person2 {
constructor(readonly name: string) {
}
}
const p = new Person2('jack')
console.log(p.name)
注意看我們是如何舍棄參數
name
,僅在構造函數裡使用
readonly name: string
參數來建立和初始化
name
成員。 我們把聲明和指派合并至一處。
參數屬性通過給構造函數參數前面添加一個通路限定符來聲明。使用
private
限定一個參數屬性會聲明并初始化一個私有成員;對于
public
和
protected
來說也是一樣。
存取器
TypeScript
支援通過
getters/setters
來截取對對象成員的通路。 它能幫助你有效的控制對對象成員的通路。
下面來看如何把一個簡單的類改寫成使用
get
和
set
。 首先,我們從一個沒有使用存取器的例子開始。
class Person {
firstName: string = 'A'
lastName: string = 'B'
get fullName () {
return this.firstName + '-' + this.lastName
}
set fullName (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
const p = new Person()
console.log(p.fullName)
p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName)
p.fullName = 'E-F'
console.log(p.firstName, p.lastName)
靜态屬性
到目前為止,我們隻讨論了類的執行個體成員,那些僅當類被執行個體化的時候才會被初始化的屬性。 我們也可以建立類的靜态成員,這些屬性存在于類本身上面而不是類的執行個體上。 在這個例子裡,我們使用
static
定義
origin
,因為它是所有網格都會用到的屬性。 每個執行個體想要通路這個屬性的時候,都要在
origin
前面加上類名。 如同在執行個體屬性上使用
this.xxx
來通路屬性一樣,這裡我們使用
Grid.xxx
來通路靜态屬性。
/*
靜态屬性, 是類對象的屬性
非靜态屬性, 是類的執行個體對象的屬性
*/
class Person {
name1: string = 'A'
static name2: string = 'B'
}
console.log(Person.name2)
console.log(new Person().name1)
抽象類
抽象類做為其它派生類的基類使用。 它們不能被執行個體化。不同于接口,抽象類可以包含成員的實作細節。
abstract
關鍵字是用于定義抽象類和在抽象類内部定義抽象方法。
/*
抽象類
不能建立執行個體對象, 隻有實作類才能建立執行個體
可以包含未實作的抽象方法
*/
abstract class Animal {
abstract cry ()
run () {
console.log('run()')
}
}
class Dog extends Animal {
cry () {
console.log(' Dog cry()')
}
}
const dog = new Dog()
dog.cry()
dog.run()
4. 函數
函數是 JavaScript 應用程式的基礎,它幫助你實作抽象層,模拟類,資訊隐藏和子產品。在 TypeScript 裡,雖然已經支援類,命名空間和子產品,但函數仍然是主要的定義行為的地方。TypeScript 為 JavaScript 函數添加了額外的功能,讓我們可以更容易地使用。
基本示例
和 JavaScript 一樣,TypeScript 函數可以建立有名字的函數和匿名函數。你可以随意選擇适合應用程式的方式,不論是定義一系列 API 函數還是隻使用一次的函數。
通過下面的例子可以迅速回想起這兩種 JavaScript 中的函數:
// 命名函數
function add(x, y) {
return x + y
}
// 匿名函數
let myAdd = function(x, y) {
return x + y;
}
函數類型
-
為函數定義類型
讓我們為上面那個函數添加類型:
function add(x: number, y: number): number {
return x + y
}
let myAdd = function(x: number, y: number): number {
return x + y
}
我們可以給每個參數添加類型之後再為函數本身添加傳回值類型。TypeScript 能夠根據傳回語句自動推斷出傳回值類型。
-
書寫完整函數類型
現在我們已經為函數指定了類型,下面讓我們寫出函數的完整類型。
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
可選參數和預設參數
TypeScript 裡的每個函數參數都是必須的。 這不是指不能傳遞
null
或
undefined
作為參數,而是說編譯器檢查使用者是否為每個參數都傳入了值。編譯器還會假設隻有這些參數會被傳遞進函數。 簡短地說,傳遞給一個函數的參數個數必須與函數期望的參數個數一緻。
JavaScript 裡,每個參數都是可選的,可傳可不傳。 沒傳參的時候,它的值就是
undefined
。 在TypeScript 裡我們可以在參數名旁使用
?
實作可選參數的功能。 比如,我們想讓
lastName
是可選的:
在 TypeScript 裡,我們也可以為參數提供一個預設值當使用者沒有傳遞這個參數或傳遞的值是
undefined
時。 它們叫做有預設初始化值的參數。 讓我們修改上例,把
firstName
的預設值設定為
"A"
。
function buildName(firstName: string='A', lastName?: string): string {
if (lastName) {
return firstName + '-' + lastName
} else {
return firstName
}
}
console.log(buildName('C', 'D'))
console.log(buildName('C'))
console.log(buildName())
剩餘參數
必要參數,預設參數和可選參數有個共同點:它們表示某一個參數。 有時,你想同時操作多個參數,或者你并不知道會有多少參數傳遞進來。 在 JavaScript 裡,你可以使用
arguments
來通路所有傳入的參數。
在 TypeScript 裡,你可以把所有參數收集到一個變量裡:
剩餘參數會被當做個數不限的可選參數。 可以一個都沒有,同樣也可以有任意個。 編譯器建立參數數組,名字是你在省略号(
...
)後面給定的名字,你可以在函數體内使用這個數組。
function info(x: string, ...args: string[]) {
console.log(x, args)
}
info('abc', 'c', 'b', 'a')
函數重載
函數重載: 函數名相同, 而形參不同的多個函數
在JS中, 由于弱類型的特點和形參與實參可以不比對, 是沒有函數重載這一說的 但在TS中, 與其它面向對象的語言(如Java)就存在此文法
/*
函數重載: 函數名相同, 而形參不同的多個函數
需求: 我們有一個add函數,它可以接收2個string類型的參數進行拼接,也可以接收2個number類型的參數進行相加
*/
// 重載函數聲明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定義函數實作
function add(x: string | number, y: string | number): string | number {
// 在實作上我們要注意嚴格判斷兩個參數的類型是否相等,而不能簡單的寫一個 x + y
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
// console.log(add(1, 'a')) // error
5. 泛型
指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定具體類型的一種特性。
引入
下面建立一個函數, 實作功能: 根據指定的數量
count
和資料
value
, 建立一個包含
count
個
value
的數組 不用泛型的話,這個函數可能是下面這樣:
function createArray(value: any, count: number): any[] {
const arr: any[] = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr1 = createArray(11, 3)
const arr2 = createArray('aa', 3)
console.log(arr1[0].toFixed(), arr2[0].split(''))
使用函數泛型
function createArray2 <T> (value: T, count: number) {
const arr: Array<T> = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr3 = createArray2<number>(11, 3)
console.log(arr3[0].toFixed())
// console.log(arr3[0].split('')) // error
const arr4 = createArray2<string>('aa', 3)
console.log(arr4[0].split(''))
// console.log(arr4[0].toFixed()) // error
多個泛型參數的函數
一個函數可以定義多個泛型參數
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
泛型接口
在定義接口時, 為接口中的屬性或方法定義泛型類型
在使用接口時, 再指定具體的泛型類型
interface IbaseCRUD <T> {
data: T[]
add: (t: T) => void
getById: (id: number) => T
}
class User {
id?: number; //id主鍵自增
name: string; //姓名
age: number; //年齡
constructor (name, age) {
this.name = name
this.age = age
}
}
class UserCRUD implements IbaseCRUD <User> {
data: User[] = []
add(user: User): void {
user = {...user, id: Date.now()}
this.data.push(user)
console.log('儲存user', user.id)
}
getById(id: number): User {
return this.data.find(item => item.id===id)
}
}
const userCRUD = new UserCRUD()
userCRUD.add(new User('tom', 12))
userCRUD.add(new User('tom2', 13))
console.log(userCRUD.data)
泛型類
在定義類時, 為類中的屬性或方法定義泛型類型 在建立類的執行個體時, 再指定特定的泛型類型
class GenericNumber<T> {
zeroValue: T
add: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function(x, y) {
return x + y
}
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
return x + y
}
console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
泛型限制
如果我們直接對一個泛型參數取
length
屬性, 會報錯, 因為這個泛型根本就不知道它有這個屬性
// 沒有泛型限制
function fn <T>(x: T): void {
// console.log(x.length) // error
}
我們可以使用泛型限制來實作
interface Lengthwise {
length: number;
}
// 指定泛型限制
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
我們需要傳入符合限制類型的值,必須包含必須
length
屬性:
fn2('abc')
// fn2(123) // error number沒有length屬性