Typescript學習教程,從入門到精通,TypeScript 泛型與類型操作詳解(一)(16)

TypeScript 泛型與類型操作詳解(一)

TypeScript 提供了強大的類型系統,其中泛型(Generics)和類型操作(Type Manipulation)是其核心特性之一。本文將詳細介紹 TypeScript 中的泛型及其相關概念,并通過案例代碼進行說明。


一、泛型簡介

泛型允許在定義函數、接口或類時,不預先指定具體的類型,而是在使用時指定類型。這種方式提高了代碼的復用性和靈活性。

為什么使用泛型

  1. 提高代碼重用性
  2. 提供更好的類型安全性
  3. 減少使用 any 類型的需要
  4. 在編譯時捕獲類型錯誤

1.1 形式類型參數與實際類型參數

  • 形式類型參數(Type Parameters):在泛型定義中聲明的類型占位符,通常使用 <T><T, U> 等形式。
  • 實際類型參數(Type Arguments):在使用泛型時,傳入的具體類型。

示例:

// 定義一個泛型函數,形式類型參數為 T
function identity<T>(arg: T): T {return arg;
}// 使用泛型函數,實際類型參數為 number
let output = identity<number>(42); // output 的類型為 number// 也可以讓 TypeScript 推斷類型
let output2 = identity("Hello, TypeScript!"); // output2 的類型為 string

1.2 泛型約束

有時需要對泛型參數進行約束,限制其必須符合某些條件。可以使用 extends 關鍵字實現。

示例:

// 定義一個接口,描述必須具有 length 屬性的類型
interface Lengthwise {length: number;
}// 使用泛型約束,限制 T 必須符合 Lengthwise 接口
function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length); // 現在可以安全地使用 length 屬性return arg;
}// 正確使用
loggingIdentity("Hello"); // string 符合 Lengthwise 接口// 錯誤使用,會在編譯時報錯
// loggingIdentity(42); // number 不符合 Lengthwise 接口

二、泛型函數

泛型函數允許函數在調用時指定類型參數,從而實現更靈活的參數和返回值類型。

示例:

// 泛型函數,返回第一個元素
function getFirstElement<T>(arr: T[]): T {return arr[0];
}let firstNumber = getFirstElement<number>([1, 2, 3]); // firstNumber 的類型為 number
let firstString = getFirstElement<string>(["a", "b", "c"]); // firstString 的類型為 string

三、泛型接口

接口也可以是泛型的,用于定義泛型函數的類型或泛型對象的結構。

示例:

// 定義一個泛型接口,描述一個包含鍵值對的對象
interface Pair<K, V> {key: K;value: V;
}// 使用泛型接口
let user: Pair<string, number> = {key: "age",value: 30
};

四、泛型類型別名

類型別名可以使用泛型來創建可復用的復雜類型。

示例:

// 定義一個泛型類型別名,描述一個數組或對象
type Container<T> = T[] | { value: T };// 使用泛型類型別名
let numberContainer: Container<number> = [1, 2, 3];
let stringContainer: Container<string> = { value: "Hello" };

五、泛型類

類也可以是泛型的,用于創建可復用的組件。

示例:

// 定義一個泛型類,表示一個棧
class Stack<T> {private elements: T[] = [];push(element: T) {this.elements.push(element);}pop(): T | undefined {return this.elements.pop();}peek(): T | undefined {return this.elements[this.elements.length - 1];}
}// 使用泛型類
let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.peek()); // 輸出: 2let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek()); // 輸出: b

六、局部類型

局部類型是指在函數或塊作用域內定義的類型。

示例:

function process<T>(value: T) {// 定義一個局部類型type Wrapped = { wrapped: T };let wrappedValue: Wrapped = { wrapped: value };console.log(wrappedValue);
}process("Hello"); // 輸出: { wrapped: "Hello" }

七、聯合類型

聯合類型表示一個值可以是幾種類型之一,使用 | 分隔。

示例:

function printId(id: number | string) {console.log("ID:", id);
}printId(101); // 輸出: ID: 101
printId("202"); // 輸出: ID: 202

7.1 聯合類型字面量

聯合類型字面量是指字面量值的聯合。

示例:

type Direction = "left" | "right" | "up" | "down";function move(direction: Direction) {console.log("Moving", direction);
}move("left"); // 輸出: Moving left
// move("forward"); // 編譯錯誤

7.2 聯合類型的類型成員

聯合類型的每個成員類型必須符合所有成員類型的共有屬性。

示例:

interface Bird {fly(): void;layEggs(): void;
}interface Fish {swim(): void;layEggs(): void;
}function getSmallPet(): Bird | Fish {// ...
}let pet = getSmallPet();
pet.layEggs(); // 正確
// pet.fly(); // 錯誤: Fish 類型沒有 fly 方法

八、交叉類型

交叉類型表示一個值同時符合幾種類型,使用 & 分隔。

示例:

interface Person {name: string;
}interface Employee {employeeId: number;
}type PersonEmployee = Person & Employee;let personEmployee: PersonEmployee = {name: "Alice",employeeId: 123
};

8.1 交叉類型字面量

交叉類型字面量是指字面量值的交叉。

示例:

type Color = "red" & "blue"; // 實際上是一個空類型,因為沒有值同時是 "red" 和 "blue"let color: Color;
// 無法實例化,因為沒有值符合 Color 類型

8.2 交叉類型的類型成員

交叉類型的成員類型必須符合所有成員類型的屬性。

示例:

interface A {a: string;
}interface B {b: number;
}type C = A & B;let c: C = {a: "Hello",b: 42
};

九、交叉類型與聯合類型

交叉類型和聯合類型可以組合使用,以創建更復雜的類型。

示例:

interface Dog {bark(): void;
}interface Cat {meow(): void;
}type DogCat = Dog & Cat;function getDogCat(): DogCat {return {bark() {console.log("Woof!");},meow() {console.log("Meow!");}};
}let pet: DogCat = getDogCat();
pet.bark(); // 輸出: Woof!
pet.meow(); // 輸出: Meow!

十、索引類型

索引類型允許在泛型中使用動態屬性訪問。

10.1 索引類型查詢

使用 keyof 操作符獲取一個類型的鍵的聯合類型。

示例:

interface Person {name: string;age: number;address: string;
}type PersonKeys = keyof Person; // "name" | "age" | "address"

10.2 索引訪問類型

使用索引訪問類型獲取某個屬性的類型。

示例:

type NameType = Person["name"]; // string
type AgeType = Person["age"]; // number

10.3 索引類型的應用

結合泛型和索引類型,實現更靈活的類型操作。

示例:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];
}let person: Person = {name: "Bob",age: 25,address: "123 Main St"
};let name = getProperty(person, "name"); // name 的類型為 string
let age = getProperty(person, "age"); // age 的類型為 number

十一、映射對象類型

映射對象類型允許動態地創建對象類型,基于已有的類型進行轉換。

11.1 映射對象類型聲明

使用 in 關鍵字和 keyof 操作符來聲明映射對象類型。

示例:

type ReadonlyPerson = {readonly [K in keyof Person]: Person[K]
};let readonlyPerson: ReadonlyPerson = {name: "Charlie",age: 30,address: "456 Elm St"
};// readonlyPerson.age = 31; // 錯誤: 屬性 age 是只讀的

11.2 映射對象類型解析

映射對象類型通過遍歷鍵并應用轉換函數來生成新的類型。

示例:

type Stringify<T> = {[K in keyof T]: string;
};type StringifiedPerson = Stringify<Person>;let stringifiedPerson: StringifiedPerson = {name: "Dave",age: "25",address: "789 Oak St"
};

11.3 映射對象類型的應用

結合泛型和映射對象類型,實現更復雜的類型操作。

示例:

// 定義一個泛型函數,將對象的屬性值轉換為字符串
function stringifyValues<T>(obj: T): { [K in keyof T]: string } {let result = {} as { [K in keyof T]: string };for (let key in obj) {result[key] = String(obj[key]);}return result;
}let person: Person = {name: "Eve",age: 22,address: "321 Pine St"
};let stringifiedPerson = stringifyValues(person);
// stringifiedPerson 的類型為 { name: string; age: string; address: string }

十二、同態映射對象類型

同態映射對象類型是指在映射過程中保持原有類型的結構。

示例:

// 定義一個泛型函數,創建一個只讀版本的映射對象類型
function makeReadonly<T>(obj: T): { readonly [K in keyof T]: T[K] } {return Object.freeze(obj);
}let person: Person = {name: "Frank",age: 28,address: "654 Cedar St"
};let readonlyPerson = makeReadonly(person);
// readonlyPerson 的屬性是只讀的
// readonlyPerson.age = 29; // 錯誤: 屬性 age 是只讀的

總結

TypeScript 的泛型與類型操作提供了強大的工具,使得開發者能夠編寫靈活、可復用的代碼。通過理解和掌握這些概念,可以顯著提升代碼質量和開發效率。

案例代碼匯總

以下是上述各個部分的完整代碼示例:

// 泛型函數
function identity<T>(arg: T): T {return arg;
}let output = identity<number>(42);
let output2 = identity("Hello, TypeScript!");// 泛型約束
interface Lengthwise {length: number;
}function loggingIdentity<T extends Lengthwise>(arg: T): T {console.log(arg.length);return arg;
}// 泛型接口
interface Pair<K, V> {key: K;value: V;
}let user: Pair<string, number> = {key: "age",value: 30
};// 泛型類型別名
type Container<T> = T[] | { value: T };let numberContainer: Container<number> = [1, 2, 3];
let stringContainer: Container<string> = { value: "Hello" };// 泛型類
class Stack<T> {private elements: T[] = [];push(element: T) {this.elements.push(element);}pop(): T | undefined {return this.elements.pop();}peek(): T | undefined {return this.elements[this.elements.length - 1];}
}let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.peek());let stringStack = new Stack<string>();
stringStack.push("a");
stringStack.push("b");
console.log(stringStack.peek());// 局部類型
function process<T>(value: T) {type Wrapped = { wrapped: T };let wrappedValue: Wrapped = { wrapped: value };console.log(wrappedValue);
}process("Hello");// 聯合類型
function printId(id: number | string) {console.log("ID:", id);
}printId(101);
printId("202");// 聯合類型字面量
type Direction = "left" | "right" | "up" | "down";function move(direction: Direction) {console.log("Moving", direction);
}move("left");// 交叉類型
interface Person {name: string;
}interface Employee {employeeId: number;
}type PersonEmployee = Person & Employee;let personEmployee: PersonEmployee = {name: "Alice",employeeId: 123
};// 索引類型
interface Person {name: string;age: number;address: string;
}type PersonKeys = keyof Person;function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];
}let person: Person = {name: "Bob",age: 25,address: "123 Main St"
};let name = getProperty(person, "name");
let age = getProperty(person, "age");// 映射對象類型
type ReadonlyPerson = {readonly [K in keyof Person]: Person[K]
};let readonlyPerson: ReadonlyPerson = {name: "Charlie",age: 30,address: "456 Elm St"
};// 映射對象類型解析
type Stringify<T> = {[K in keyof T]: string;
};type StringifiedPerson = Stringify<Person>;let stringifiedPerson: StringifiedPerson = {name: "Dave",age: "25",address: "789 Oak St"
};// 映射對象類型的應用
function stringifyValues<T>(obj: T): { [K in keyof T]: string } {let result = {} as { [K in keyof T]: string };for (let key in obj) {result[key] = String(obj[key]);}return result;
}let person: Person = {name: "Eve",age: 22,address: "321 Pine St"
};let stringifiedPerson2 = stringifyValues(person);// 同態映射對象類型
function makeReadonly<T>(obj: T): { readonly [K in keyof T]: T[K] } {return Object.freeze(obj);
}let person2: Person = {name: "Frank",age: 28,address: "654 Cedar St"
};let readonlyPerson2 = makeReadonly(person2);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/907607.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/907607.shtml
英文地址,請注明出處:http://en.pswp.cn/news/907607.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

電網即插即用介紹

一、統一設備信息模型與標準接口 實現即插即用功能的基礎在于建立統一的設備信息模型。不同廠家生產的各類電網設備&#xff0c;其內部結構、通信協議、數據格式等往往千差萬別。通過制定統一的設備信息模型&#xff0c;能夠對設備的各種屬性、功能以及接口進行標準化定義&…

核心機制:確認應答和超時重傳

核心機制一:確認應答 實現讓發送方知道接受方是否收到數據 發送方發送了數據之后,接受方,一旦接收到了,就會給發送方返回一個"應答報文"告訴發送方"我已經收到了數據" 網絡上會出現"后發先至"的情況 為了解決上述問題,就引入了"序號和確…

spring openfeign

pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http…

從零到一選擇AI自動化平臺:深度解析n8n、Dify與Coze

隨著人工智能&#xff08;AI&#xff09;技術的快速發展&#xff0c;越來越多的企業和開發者開始探索AI驅動的自動化解決方案。面對市場上琳瑯滿目的平臺&#xff0c;如何選擇適合自己的AI自動化工具成為了一個重要的問題。在這篇文章中&#xff0c;我們將從功能、應用場景、易…

“以光惠算”走進校園,湖北大學用F5G-A全光網賦能智慧校園

SUN的聯合創始人約翰蓋奇&#xff0c;曾在1984年提出過一個大膽的猜想——“網絡就是計算機”。 到了大模型時代&#xff0c;40多年前的猜想被賦予了新的內涵。大模型訓練和推理所需的資源&#xff0c;遠超單臺計算機的承載能力&#xff0c;涌現出了新的網絡范式&#xff1a;大…

飛牛fnNAS的Docker應用之迅雷篇

目錄 一、“迅雷”應用安裝 二、啟動迅雷 三、迅雷賬號登錄 四、修改“迅雷”下載保存路徑 1、下載路徑準備 2、停止“迅雷”Docker容器 3、修改存儲位置 4、重新啟動Docker容器 5、再次“啟用”迅雷 五、測試 1、在PC上添加下載任務 2、手機上管理 3、手機添加下…

編程技能:格式化打印01,vsprintf 函數族簡介

專欄導航 本節文章分別屬于《Win32 學習筆記》和《MFC 學習筆記》兩個專欄&#xff0c;故劃分為兩個專欄導航。讀者可以自行選擇前往哪個專欄。 &#xff08;一&#xff09;WIn32 專欄導航 上一篇&#xff1a;編程技能&#xff1a;字符串函數14&#xff0c;memset 回到目錄…

PECVD 生成 SiO? 的反應方程式

在PECVD工藝中&#xff0c;沉積氧化硅薄膜以SiH?基與TEOS基兩種工藝路線為主。 IMD Oxide&#xff08;USG&#xff09; 這部分主要沉積未摻雜的SiO?&#xff0c;也叫USG&#xff08;Undoped Silicate Glass&#xff09;&#xff0c;常用于IMD&#xff08;Inter-Metal Diele…

[IMX] 10.串行外圍設備接口 - SPI

代碼鏈接&#xff1a;GitHub - maoxiaoxian/imx 參考資料&#xff1a; https://zhuanlan.zhihu.com/p/290620901 SPI協議詳解 - bujidao1128 - 博客園 SPI總線協議及SPI時序圖詳解 - Ady Lee - 博客園 目錄 1.SPI 簡介 2.I.MX6U ECSPI 簡介 2.1.控制寄存器 1 - ECSPIx_CO…

基于Docker和YARN的大數據環境部署實踐最新版

基于Docker和YARN的大數據環境部署實踐 目的 本操作手冊旨在指導用戶通過Docker容器技術&#xff0c;快速搭建一個完整的大數據環境。該環境包含以下核心組件&#xff1a; Hadoop HDFS/YARN&#xff08;分布式存儲與資源調度&#xff09;Spark on YARN&#xff08;分布式計算…

Java設計模式之中介者模式詳解

Java設計模式之中介者模式詳解 一、中介者模式核心思想 核心目標&#xff1a;通過中介對象封裝一組對象間的交互&#xff0c;將網狀的對象關系轉變為星型結構。如同機場控制塔協調所有飛機的起降&#xff0c;避免飛機之間直接通信導致的混亂。 二、中介者模式類圖&#xff08;…

ArcGIS應用指南:基于網格與OD成本矩陣的交通可達性分析

隨著城市化進程的加速,交通系統的效率和公平性日益成為影響居民生活質量的關鍵因素之一。在這一背景下,如何科學評估城市區域內的交通可達性,成為了城市規劃、交通管理和公共政策制定中的重要議題。作為中國東南沿海的重要港口城市,廈門以其獨特的地理優勢和快速的城市發展…

基于NXP例程學習CAN UDS刷寫流程

文章目錄 前言1.概述1.1 診斷報文 2.協議數據單元(N_PDU)2.1 尋址信息&#xff08;N_AI&#xff09;2.1.1 物理尋址2.1.2 功能尋址2.1.3 常規尋址&#xff08;Normal addressing&#xff09;2.1.4 常規固定尋址&#xff08;Normal fixed addressing&#xff09;2.1.5 擴展尋址&…

近期手上的一個基于Function Grap(類AWS的Lambda)小項目的改造引發的思考

函數式Function是云計算里最近幾年流行起來的新的架構和模式&#xff0c;因為它不依賴云主機&#xff0c;非常輕量&#xff0c;按需使用&#xff0c;甚至是免費使用&#xff0c;特別適合哪種數據同步&#xff0c;數據轉發&#xff0c;本身不需要保存數據的業務場景&#xff0c;…

什么是 SQL 注入?如何防范?

什么是 SQL 注入?如何防范? 1. SQL 注入概述 1.1 基本定義 SQL 注入(SQL Injection)是一種通過將惡意SQL 語句插入到應用程序的輸入參數中,從而欺騙服務器執行非預期SQL命令的攻擊技術。攻擊者可以利用此漏洞繞過認證、竊取數據甚至破壞數據庫。 關鍵結論:SQL 注入是O…

高德地圖應用OceanBase單元化構建下一代在線地圖服務

IEEE International Conference on Data Engineering (ICDE) 是數據庫和數據工程領域的頂級學術會議之一&#xff08;與SIGMOD、VLDB并成為數據庫三大頂會&#xff09;&#xff0c;自1984年首次舉辦以來&#xff0c;每年舉辦一次。ICDE涵蓋廣泛的主題&#xff0c;包括數據庫系統…

Vue3中Element-Plus中el-input及el-select 邊框樣式

如果不需要顯示下邊框&#xff0c;純無邊框直接將 【border-bottom: 1px solid #C0C4CC; 】注掉或去掉即可。 正常引用組件使用即可&#xff0c;無須自定義樣式&#xff0c;最終效果CSS樣式。 <style scoped> /* 輸入框的樣式 */ :deep(.el-input__wrapper) { box-sha…

如何做好一份技術文檔:從信息孤島到知識圖譜的進階之路

如何做好一份技術文檔&#xff1a;從信息孤島到知識圖譜的進階之路 在軟件開發的漫長征程中&#xff0c;技術文檔如同隱藏在代碼叢林中的路標&#xff0c;不僅指引著開發團隊的前行方向&#xff0c;更在產品迭代的歲月里構筑起知識傳承的橋梁。一份優質的技術文檔&#xff0c;既…

Docker Compose使用自定義用戶名密碼啟動Redis

通常我們使用下面的命令來啟動 redis 容器&#xff0c;此時連接 Redis 的時候是不需要用戶認證的 sudo docker run -d --name my-redis -p 6379:6379 redis此時我們可以使用 redis-server --requirepass "mypassword" 來指定默認用戶&#xff08;default&#xff09…

1.什么是node.js、npm、vue

一、Node.js 是什么&#xff1f; &#x1f63a; 定義&#xff1a; Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行時環境&#xff0c;讓你可以在瀏覽器之外運行 JavaScript 代碼&#xff0c;主要用于服務端開發。 &#x1f63a;從計算機底層說&#xff1a;什么是“運…