最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

TypeScript前端的必修課之從JS到TS詳解

 更新時(shí)間:2026年05月09日 08:34:41   作者:昭昭頌桉a  
許多開發(fā)者對TypeScript是否值得學(xué)習(xí)感到困惑,答案是肯定的,TypeScript(TS)為前端開發(fā)提供了強(qiáng)大的類型系統(tǒng),有助于減少錯(cuò)誤并提升代碼質(zhì)量,這篇文章主要介紹了TypeScript前端的必修課之從JS到TS的相關(guān)資料,需要的朋友可以參考下,

1. TypeScript 是什么?為什么要學(xué)?

1.1 一個(gè) bug 引發(fā)的思考

假設(shè)你要寫一個(gè)函數(shù),計(jì)算商品打折后的價(jià)格:

// 商品打折函數(shù)
function getDiscountedPrice(price, discount) {
  return price - price * discount
}

// 調(diào)用
getDiscountedPrice(100, 0.2)  // 80,正確
getDiscountedPrice('100', '0.2')  // 80?還是報(bào)錯(cuò)?

問題: getDiscountedPrice('100', '0.2') 的結(jié)果是 80,還是 NaN?還是 報(bào)錯(cuò)?

答案是 80!因?yàn)?JavaScript 偷偷幫你把字符串轉(zhuǎn)成了數(shù)字。

但這不是好事——如果用戶真的傳了字符串進(jìn)來,你的程序可能計(jì)算出錯(cuò)誤的結(jié)果,而 JavaScript 一聲不吭。

更危險(xiǎn)的場景是

function getUser(id) {
  const user = users.find(u => u.id === id)
  return user.name  // 如果 user 是 undefined,這里會(huì)報(bào)錯(cuò)
}

如果 id 傳錯(cuò)了,user 是 undefined,然后 user.name 就會(huì)報(bào)錯(cuò):

 TypeError: Cannot read property 'name' of undefined

但 JavaScript 只在運(yùn)行到這行時(shí)才報(bào)錯(cuò)。如果這個(gè)函數(shù)很少被調(diào)用,bug 可能藏了很久才發(fā)現(xiàn)。

這就是動(dòng)態(tài)類型的痛點(diǎn)。

1.2 JavaScript 的痛點(diǎn)

1. 類型隱式轉(zhuǎn)換

console.log('5' - 3)   // 2(字符串轉(zhuǎn)數(shù)字)
console.log('5' + 3)   // '53'(數(shù)字轉(zhuǎn)字符串)
console.log(true + 1)  // 2(true 轉(zhuǎn)成 1)
console.log([] + [])   // ''(空字符串)

同樣的 +,不同的結(jié)果。你永遠(yuǎn)不知道傳入的是什么類型。

2. 訪問不存在的屬性

const user = null
console.log(user.name)  // 運(yùn)行時(shí)才報(bào)錯(cuò)

3. 函數(shù)參數(shù)類型錯(cuò)誤

function greet(name) {
  return `Hello, ${name.toUpperCase()}`
}

greet('張三')  // "Hello, 張三"
greet(123)     // 運(yùn)行時(shí)才報(bào)錯(cuò):name.toUpperCase is not a function

4. 重構(gòu)困難

// 原來 user 有 name 屬性
function showUser(user) {
  console.log(user.name)
}

// 后來改成了 username
// 需要手動(dòng)找到所有調(diào)用 showUser 的地方...
// 漏掉一個(gè),線上就報(bào)錯(cuò)

總結(jié):JavaScript 很靈活,但靈活意味著 你犯錯(cuò),它不提醒。

1.3 TypeScript 是什么?

1. 定義

TypeScript = JavaScript + 類型系統(tǒng)

TypeScript 是 JavaScript 的超集,所有合法的 JS 代碼,在 TS 里也是合法的。

TypeScript:TS 包含 JS 的所有特性,類型系統(tǒng)(TS 獨(dú)有的, 類型注解、接口、泛型、類型推斷... )

2. 工作流程

你寫的 TS 代碼  —> TypeScript 編譯器(檢查類型錯(cuò)誤)—> 編譯成 JavaScript —> 瀏覽器/Node.js 運(yùn)行

關(guān)鍵點(diǎn):瀏覽器不認(rèn)識(shí) TS,需要先編譯成 JS。類型檢查發(fā)生在編譯時(shí),不是運(yùn)行時(shí)。

1.4 TS 能解決什么問題?

1. 類型錯(cuò)誤提前發(fā)現(xiàn)

function getDiscountedPrice(price: number, discount: number): number {
  return price - price * discount
}

getDiscountedPrice(100, 0.2)      // 正確
getDiscountedPrice('100', '0.2')  // 編譯時(shí)就報(bào)錯(cuò)

2. 智能提示

interface User {
  name: string
  age: number
}

const user: User = { name: '張三', age: 18 }
user.  // 輸入 . 后,編輯器會(huì)提示 name 和 age

3. 重構(gòu)安全

// 原來 user 有 name 屬性
function showUser(user: { name: string }) {
  console.log(user.name)
}

// 改成 username 后
function showUser(user: { username: string }) {
  console.log(user.username)  // 這里改了
}

// 調(diào)用處會(huì)報(bào)錯(cuò),TS 幫你找全所有地方
showUser({ name: '張三' })  // 報(bào)錯(cuò):name 不在類型中

4. 自文檔化

// JS 寫法:需要看代碼才知道參數(shù)是什么
function fetchData(url, options) { ... }

// TS 寫法:看一眼就知道怎么用
function fetchData(url: string, options?: {
  method?: 'GET' | 'POST'
  timeout?: number
}): Promise<any> { ... }

1.5 環(huán)境搭建與第一個(gè) TS 程序

1. 安裝 TypeScript

# 全局安裝
npm install -g typescript

# 驗(yàn)證安裝
tsc --version

2. 第一個(gè) TS 程序

創(chuàng)建一個(gè) hello.ts 文件:

// hello.ts
function greet(name: string): string {
  return `Hello, ${name}!`
}

const message = greet('TypeScript')
console.log(message)

3. 編譯運(yùn)行

# 編譯成 JS
tsc hello.ts

# 生成 hello.js,然后運(yùn)行
node hello.js

# 或者用 ts-node 直接運(yùn)行
npx ts-node hello.ts

4. 在線體驗(yàn)

不想安裝環(huán)境?直接訪問:TypeScript Playgroundhttps://www.typescriptlang.org/play

在線寫 TS,實(shí)時(shí)看編譯結(jié)果

5. 第一個(gè)報(bào)錯(cuò)

function greet(name: string): string {
  return `Hello, ${name}!`
}

greet(123)  // 報(bào)錯(cuò):參數(shù)類型不匹配

編譯時(shí)會(huì)報(bào)錯(cuò):

error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

2. 基礎(chǔ)類型

2.1 類型注解

1. 什么是類型注解?

類型注解就是告訴 TypeScript,這個(gè)變量是什么類型。

// 語法:變量名: 類型
let name: string = '張三'
let age: number = 18
let isActive: boolean = true

2. 為什么要加類型注解?

// 不加注解:TS 會(huì)自己推斷
let name = '張三'  // TS 推斷 name 是 string 類型
name = 123         // 報(bào)錯(cuò):不能把 number 賦給 string

// 加了注解:明確告訴 TS 你的意圖
let age: number = 18
age = '二十'        // 報(bào)錯(cuò)

類型注解不是必須的,TS 會(huì)自動(dòng)推斷。但加注解可以讓代碼更清晰。

3. 函數(shù)參數(shù)和返回值

// 參數(shù)加類型,返回值也加類型
function add(a: number, b: number): number {
  return a + b
}

// 如果返回值類型不對,TS 會(huì)報(bào)錯(cuò)
function wrongAdd(a: number, b: number): number {
  return '結(jié)果'  // 報(bào)錯(cuò):不能返回 string
}

2.2 原始類型

1.字符串 string

let name: string = '張三'
let greeting: string = "Hello"
let template: string = `你好,${name}`

// 錯(cuò)誤示例
let city: string = 123  // 不能把 number 賦給 string

2. 數(shù)字 number

let age: number = 18
let price: number = 99.9
let hex: number = 0xff   // 十六進(jìn)制
let binary: number = 0b1010  // 二進(jìn)制
let infinity: number = Infinity

// 錯(cuò)誤示例
let count: number = '100'  // 不能把 string 賦給 number

3. 布爾值 boolean

let isLoggedIn: boolean = true
let isAdmin: boolean = false
let isActive: boolean = Boolean('hello')  // true

// 錯(cuò)誤示例
let isDone: boolean = 'true'  // 不能把 string 賦給 boolean

4. 類型推斷

類型注解可以不寫

// 不寫類型注解,TS 會(huì)自動(dòng)推斷
let name = '張三'      // TS 推斷 name 是 string
let age = 18           // TS 推斷 age 是 number
let isActive = true    // TS 推斷 isActive 是 boolean

// 后面賦值錯(cuò)誤類型會(huì)報(bào)錯(cuò)
name = 123  // 報(bào)錯(cuò):不能把 number 賦給 string

2.3 數(shù)組與元組

1. 數(shù)組 Array

兩種寫法:

// 寫法1:類型[]
let numbers: number[] = [1, 2, 3, 4, 5]
let names: string[] = ['張三', '李四', '王五']

// 寫法2:Array<類型>
let scores: Array<number> = [90, 85, 100]
let fruits: Array<string> = ['蘋果', '香蕉', '橙子']

// 錯(cuò)誤示例
let ages: number[] = [18, 20, '二十五']  // 不能放 string

2. 元組 Tuple

元組是固定長度、固定類型順序的數(shù)組。

// 定義元組:[string, number] 表示第一個(gè)是 string,第二個(gè)是 number
let user: [string, number] = ['張三', 18]

// 正確
user = ['李四', 20]

// 錯(cuò)誤
user = [18, '李四']     // 順序錯(cuò)了
user = ['王五']         // 長度不夠
user = ['趙六', 22, '北京']  // 長度超了

什么時(shí)候用元組?

// 模擬 API 響應(yīng)
let response: [boolean, string] = [true, '請求成功']

// 坐標(biāo)
let position: [number, number] = [100, 200]

// CSV 行數(shù)據(jù)
let row: [string, number, boolean] = ['張三', 18, true]

2.4 any、unknown、void、never

1. 任意類型 any

any 表示「可以是任何類型」,相當(dāng)于關(guān)閉了類型檢查

let something: any = 'hello'
something = 123        //  可以
something = true       //  可以
something.toUpperCase() //  可以(雖然 number 沒有 toUpperCase)

// 危險(xiǎn):用了 any,TS 的保護(hù)就沒了
let data: any = { name: '張三' }
console.log(data.age.toFixed())  // 運(yùn)行時(shí)才報(bào)錯(cuò),TS 不阻止

盡量少用 any,它會(huì)讓你失去 TS 的保護(hù)。

2. 未知類型 unknown

unknown 是 any 的安全版本。

let userInput: unknown = 'hello'

// unknown 不能直接使用
userInput.toUpperCase()  // 報(bào)錯(cuò):unknown 不能直接調(diào)用方法

// 需要先「收窄類型」
if (typeof userInput === 'string') {
  userInput.toUpperCase()  // 現(xiàn)在可以了
}

any vs unknown:any 放棄了檢查,unknown 要求你先檢查再使用。

3. 無返回值 void

void 表示函數(shù)沒有返回值。

// 沒有 return
function logMessage(message: string): void {
  console.log(message)
  // 沒有 return,或者 return; 或者 return undefined;
}

// 有 return,但返回 undefined
function doNothing(): void {
  return
}

// 錯(cuò)誤示例
function wrong(): void {
  return 'hello'  // 不能返回 string
}

4. 永遠(yuǎn)不會(huì)發(fā)生 never

never 表示函數(shù)永遠(yuǎn)不會(huì)有返回值(要么一直運(yùn)行,要么拋出錯(cuò)誤)。

// 拋出錯(cuò)誤,不會(huì)正常結(jié)束
function throwError(message: string): never {
  throw new Error(message)
}

// 無限循環(huán),不會(huì)結(jié)束
function infiniteLoop(): never {
  while (true) {
    console.log('無限循環(huán)')
  }
}

// 不可能到達(dá)的分支
function checkType(value: string | number) {
  if (typeof value === 'string') {
    // value 是 string
  } else if (typeof value === 'number') {
    // value 是 number
  } else {
    // 這里 value 是 never,因?yàn)椴豢赡艿竭@兒
    const exhaust: never = value
  }
}

2.5 聯(lián)合類型與類型別名

1. 聯(lián)合類型 Union Type

一個(gè)變量可能是多種類型之一,用 | 分隔。

// id 可以是 string 或 number
let id: string | number = 'abc123'
id = 456  // 可以
id = true // 不能 是 boolean

// 函數(shù)參數(shù)可以是多種類型
function formatValue(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase()
  } else {
    return value.toFixed(2)
  }
}

formatValue('hello')  // "HELLO"
formatValue(3.1415)   // "3.14"

2. 類型別名 Type Alias

給類型起個(gè)名字,方便復(fù)用。

// 定義類型別名
type UserID = string | number
type User = {
  id: UserID
  name: string
  age: number
}

// 使用
let userId: UserID = 'abc123'
let user: User = {
  id: 1,
  name: '張三',
  age: 18
}

// 更復(fù)雜的例子
type Status = 'pending' | 'success' | 'error'
type ApiResponse = {
  status: Status
  data: any
  message: string
}

function handleResponse(response: ApiResponse) {
  if (response.status === 'success') {
    console.log('成功:', response.data)
  } else if (response.status === 'error') {
    console.error('失敗:', response.message)
  }
}

2.6 類型推斷

不寫類型也行?

1. 自動(dòng)推斷

TypeScript 很聰明,很多情況下不需要你寫類型注解。

// 變量推斷
let name = '張三'      // 自動(dòng)推斷為 string
let age = 18           // 自動(dòng)推斷為 number
let isActive = true    // 自動(dòng)推斷為 boolean

// 數(shù)組推斷
let numbers = [1, 2, 3]        // 推斷為 number[]
let mixed = [1, 'hello', true] // 推斷為 (string | number | boolean)[]

// 對象推斷
let user = {
  name: '張三',
  age: 18
}  // 推斷為 { name: string; age: number }

// 函數(shù)返回值推斷
function add(a: number, b: number) {
  return a + b  // 自動(dòng)推斷返回值是 number
}

2. 什么時(shí)候需要寫類型注解?

// 1. 函數(shù)參數(shù)(必須寫)
function greet(name: string) { ... }  // 參數(shù)不能推斷

// 2. 變量初始值為空時(shí)
let user: User | null = null

// 3. 函數(shù)返回值需要明確時(shí)
function getData(): Promise<User> { ... }

// 4. 類型太復(fù)雜,推斷不準(zhǔn)確時(shí)
let config: Config = { ... }

3. 最佳實(shí)踐

// 簡單情況:讓 TS 推斷
let name = '張三'
let numbers = [1, 2, 3]

// 復(fù)雜情況:手動(dòng)標(biāo)注
let user: User | null = null
function fetchData(): Promise<User[]> { ... }

// 不要過度標(biāo)注
let age: number = 18  // 多余,TS 已經(jīng)知道是 number
let age = 18  // 更好

3. 函數(shù)與類型

3.1 函數(shù)參數(shù)類型

1. 基本寫法

// 給每個(gè)參數(shù)加上類型
function greet(name: string, age: number): void {
  console.log(`${name},${age}歲`)
}

// 調(diào)用時(shí)必須傳正確類型 
greet('張三', 18)   // 正確
greet(123, 18)      // 參數(shù)1類型錯(cuò)誤
greet('張三', '18') // 參數(shù)2類型錯(cuò)誤

2. 箭頭函數(shù)

// 參數(shù)加類型,返回值加類型
const add = (a: number, b: number): number => a + b

// 如果函數(shù)體有多條語句
const multiply = (a: number, b: number): number => {
  const result = a * b
  return result
}

3. 對象參數(shù)

// 參數(shù)是對象
function printUser(user: { name: string; age: number }): void {
  console.log(`${user.name},${user.age}歲`)
}

printUser({ name: '張三', age: 18 })  // 正確

// 用接口更清晰(后面會(huì)講)
interface User {
  name: string
  age: number
}

function printUser(user: User): void {
  console.log(`${user.name},${user.age}歲`)
}

4. 回調(diào)函數(shù)參數(shù)

// 回調(diào)函數(shù)的參數(shù)也要加類型
function processData(data: string[], callback: (item: string) => void): void {
  data.forEach(item => callback(item))
}

processData(['a', 'b', 'c'], (item) => {
  console.log(item.toUpperCase())
})

3.2 返回值類型

1. 基本寫法

// 在參數(shù)括號后面加 `: 類型`
function add(a: number, b: number): number {
  return a + b
}

function greet(name: string): string {
  return `Hello, ${name}!`
}

function logMessage(msg: string): void {
  console.log(msg)
  // 沒有 return,或者 return undefined
}

2. 返回值類型推斷

// TS 可以自動(dòng)推斷返回值類型
function add(a: number, b: number) {
  return a + b  // TS 推斷返回值是 number
}

function getFullName(first: string, last: string) {
  return `${first} ${last}`  // TS 推斷返回值是 string
}

// 建議:復(fù)雜邏輯還是顯式寫返回值類型
function fetchUser(id: number): Promise<User> {
  return fetch(`/api/users/${id}`).then(res => res.json())
}

3. 返回多種類型

// 返回值可以是多種類型
function getValue(flag: boolean): string | number {
  if (flag) {
    return 'hello'
  } else {
    return 123
  }
}

const result = getValue(true)  // result 類型是 string | number

3.3 可選參數(shù)與默認(rèn)參數(shù)

1. 可選參數(shù) ?

// 用 ? 表示這個(gè)參數(shù)可以傳,也可以不傳
function greet(name: string, greeting?: string): string {
  if (greeting) {
    return `${greeting},${name}`
  } else {
    return `你好,${name}`
  }
}

greet('張三')           // "你好,張三"
greet('張三', '早上好')  // "早上好,張三"

// 可選參數(shù)必須在必選參數(shù)后面

// 錯(cuò)誤:可選參數(shù)不能在必選參數(shù)前面
function wrong(age?: number, name: string) { }

2. 默認(rèn)參數(shù)

// 參數(shù)默認(rèn)值(同時(shí)也成了可選參數(shù))
function greet(name: string, greeting: string = '你好'): string {
  return `${greeting},${name}`
}

greet('張三')           // "你好,張三"
greet('張三', '早上好')  // "早上好,張三"

// 默認(rèn)參數(shù)可以放在必選參數(shù)前面(但不推薦)
function greet(greeting: string = '你好', name: string): string {
  return `${greeting},${name}`
}
greet('張三')  // 報(bào)錯(cuò):第二個(gè)參數(shù)沒傳

3. 可選參數(shù) vs 默認(rèn)參數(shù)

對比可選參數(shù) ?默認(rèn)參數(shù) = value
不傳時(shí)undefined使用默認(rèn)值
傳 undefined 時(shí)undefined使用默認(rèn)值
傳 null 時(shí)nullnull
適用場景值可以是 undefined有明確的默認(rèn)值

3.4 剩余參數(shù)

1. 什么是剩余參數(shù)?

把多個(gè)參數(shù)收集成一個(gè)數(shù)組,用 ... 語法。

// 傳統(tǒng)方式:參數(shù)個(gè)數(shù)不固定很麻煩
function sum1(a: number, b: number, c: number) { }  // 只能3個(gè)

// 剩余參數(shù):可以傳任意多個(gè)
function sum2(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0)
}

sum2(1, 2, 3, 4, 5)  // 15
sum2(10, 20)          // 30
sum2()                // 0

2. 剩余參數(shù)與其他參數(shù)

// 剩余參數(shù)必須是最后一個(gè)參數(shù)
function introduce(name: string, age: number, ...hobbies: string[]): void {
  console.log(`${name},${age}歲`)
  console.log(`愛好:${hobbies.join('、')}`)
}

introduce('張三', 18, '讀書', '跑步', '游泳')
// 輸出:
// 張三,18歲
// 愛好:讀書、跑步、游泳

3. 剩余參數(shù)的類型

// 剩余參數(shù)是數(shù)組類型
function log(...args: string[]): void {
  args.forEach(arg => console.log(arg))
}

// 也可以是元組類型(固定長度)
function format(...args: [string, number]): string {
  return `${args[0]},${args[1]}歲`
}

format('張三', 18)   // 對
format('張三', 18, '北京')  // 錯(cuò) 只能兩個(gè)參數(shù)

3.5 函數(shù)重載

一個(gè)函數(shù)多種用法

1. 什么是重載?

同一個(gè)函數(shù)名,根據(jù)參數(shù)類型或數(shù)量的不同,執(zhí)行不同的邏輯。

// JavaScript 中常見:參數(shù)不同,行為不同
function getData(id) {
  if (typeof id === 'string') {
    // 通過字符串 ID 獲取
  } else if (typeof id === 'number') {
    // 通過數(shù)字 ID 獲取
  } else if (Array.isArray(id)) {
    // 通過 ID 數(shù)組批量獲取
  }
}

2. TypeScript 的重載寫法

// 1. 先聲明重載簽名(多個(gè))
function getData(id: string): User
function getData(id: number): User
function getData(ids: number[]): User[]

// 2. 再實(shí)現(xiàn)簽名(一個(gè))
function getData(id: any): any {
  if (typeof id === 'string') {
    // 獲取單個(gè)用戶(通過字符串 ID)
    return { id, name: '張三' }
  } else if (typeof id === 'number') {
    // 獲取單個(gè)用戶(通過數(shù)字 ID)
    return { id, name: '李四' }
  } else if (Array.isArray(id)) {
    // 獲取多個(gè)用戶
    return id.map(i => ({ id: i, name: '用戶' + i }))
  }
}

// 使用
const user1 = getData('abc')   // 類型是 User
const user2 = getData(123)     // 類型是 User
const users = getData([1, 2, 3])  // 類型是 User[]

3. 更實(shí)用的例子

// 重載簽名
function formatDate(timestamp: number): string
function formatDate(date: Date): string
function formatDate(year: number, month: number, day: number): string

// 實(shí)現(xiàn)
function formatDate(a: any, b?: any, c?: any): string {
  if (typeof a === 'number' && b === undefined && c === undefined) {
    // 時(shí)間戳
    return new Date(a).toLocaleDateString()
  } else if (a instanceof Date) {
    // Date 對象
    return a.toLocaleDateString()
  } else if (typeof a === 'number' && typeof b === 'number' && typeof c === 'number') {
    // 年月日
    return new Date(a, b - 1, c).toLocaleDateString()
  }
  return ''
}

formatDate(1704067200000)      // 2024-01-01
formatDate(new Date())          // 當(dāng)前日期
formatDate(2024, 1, 1)          // 2024-01-01

4. 什么時(shí)候用重載?

場景是否用重載
參數(shù)類型不同,邏輯差異大
參數(shù)個(gè)數(shù)不同
只是可選參數(shù)用可選參數(shù)就夠了
用聯(lián)合類型能解決優(yōu)先用聯(lián)合類型

3.6 泛型入門

讓類型也變成參數(shù)

1. 為什么需要泛型?

假設(shè)你要寫一個(gè)函數(shù),返回傳入的參數(shù):

// 只能處理 number
function identityNumber(value: number): number {
  return value
}

// 只能處理 string
function identityString(value: string): string {
  return value
}

// 用 any 可以,但失去了類型安全
function identityAny(value: any): any {
  return value
}
const result = identityAny('hello')
result.toUpperCase()  // 運(yùn)行時(shí)沒問題,但 TS 不知道類型

2. 泛型解決

用類型參數(shù)

// <T> 是一個(gè)類型變量,表示「調(diào)用時(shí)確定的類型」
function identity<T>(value: T): T {
  return value
}

// 使用
const num = identity<number>(100)     // num 類型是 number
const str = identity<string>('hello') // str 類型是 string

// TS 會(huì)自動(dòng)推斷類型,可以省略 <類型>
const num2 = identity(100)   // 自動(dòng)推斷為 number
const str2 = identity('hello') // 自動(dòng)推斷為 string

3. 泛型約束

限制類型范圍

// 不加約束:T 可以是任何類型
function getLength<T>(value: T): number {
  return value.length  // 報(bào)錯(cuò):T 可能沒有 length 屬性
}

// 加約束:T 必須有 length 屬性
interface HasLength {
  length: number
}

function getLength<T extends HasLength>(value: T): number {
  return value.length  // 可以
}

getLength('hello')      // 5(string 有 length)
getLength([1, 2, 3])    // 3(數(shù)組有 length)
getLength(123)          // 報(bào)錯(cuò):number 沒有 length

4. 多個(gè)泛型參數(shù)

// 可以同時(shí)用多個(gè)類型參數(shù)
function merge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b }
}

const result = merge({ name: '張三' }, { age: 18 })
// result 類型是 { name: string } & { age: number }
console.log(result.name, result.age)

5. 泛型在實(shí)際項(xiàng)目中的應(yīng)用

// 1. 封裝 API 請求
async function fetchApi<T>(url: string): Promise<T> {
  const response = await fetch(url)
  return response.json()
}

interface User {
  id: number
  name: string
}

const user = await fetchApi<User>('/api/users/1')
console.log(user.name)  // TS 知道 user 有 name 屬性

// 2. 封裝數(shù)組工具函數(shù)
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0]
}

const firstNumber = getFirst([1, 2, 3])  // 類型是 number | undefined
const firstString = getFirst(['a', 'b']) // 類型是 string | undefined

4. 接口與類

4.1 什么是接口?

定義對象的形狀

1. 接口的作用

接口用來定義對象應(yīng)該長什么樣,描述對象有哪些屬性、屬性是什么類型。

// 定義接口
interface User {
  name: string
  age: number
}

// 使用接口
function greet(user: User): void {
  console.log(`你好,${user.name}!你今年${user.age}歲`)
}

// 傳入的對象必須符合接口的形狀
greet({ name: '張三', age: 18 })  
greet({ name: '張三' })            //  缺少 age
greet({ name: '張三', age: 18, city: '北京' })  //  多余屬性

2. 為什么不用 type ?

// type 也能定義對象形狀
type User = {
  name: string
  age: number
}

// 但接口更推薦用于對象,有更好的錯(cuò)誤提示和擴(kuò)展性

3. 接口的實(shí)際用途

// 1. 定義 API 響應(yīng)格式
interface ApiResponse<T> {
  code: number
  data: T
  message: string
}

// 2. 定義組件 Props(React)
interface ButtonProps {
  text: string
  onClick: () => void
  disabled?: boolean
}

// 3. 定義配置對象
interface Config {
  apiUrl: string
  timeout: number
  retry: number
}

4.2 可選屬性與只讀屬性

1. 可選屬性 ?

有些屬性可以存在,也可以不存在。

interface User {
  name: string
  age: number
  email?: string    // 可選屬性
  phone?: string    // 可選屬性
}

const user1: User = { name: '張三', age: 18 }  // 沒傳 email
const user2: User = { name: '李四', age: 20, email: 'li@example.com' }  // 傳了 email

2. 只讀屬性readonly

屬性一旦賦值,就不能再修改。

interface Config {
  readonly appId: string
  readonly apiKey: string
  timeout: number  // 可以修改
}

const config: Config = {
  appId: 'abc123',
  apiKey: 'key123',
  timeout: 5000
}

config.timeout = 10000   // 可以修改
config.appId = 'newId'   // 報(bào)錯(cuò):不能修改只讀屬性

3. 實(shí)際應(yīng)用

// 1. 用戶信息(ID 不應(yīng)該被修改)
interface User {
  readonly id: number
  name: string
  email: string
}

// 2. 坐標(biāo)點(diǎn)(x, y 不應(yīng)該被修改)
interface Point {
  readonly x: number
  readonly y: number
}

// 3. 配置(不應(yīng)該被修改)
interface AppConfig {
  readonly version: string
  readonly apiUrl: string
  debug: boolean  // 這個(gè)可以動(dòng)態(tài)改
}

4.3 索引簽名

動(dòng)態(tài)屬性名

1. 什么時(shí)候需要索引簽名?

當(dāng)對象的屬性名不確定時(shí),比如字典、配置對象。

// 場景:字典對象,key 是字符串,value 是數(shù)字
interface StringMap {
  [key: string]: number
}

const scores: StringMap = {
  math: 90,
  english: 85,
  chinese: 92
}
scores.physics = 88  // 可以動(dòng)態(tài)添加

2. 索引簽名的類型

// key 是 string,value 是 string
interface Dictionary {
  [key: string]: string
}

const dict: Dictionary = {
  hello: '你好',
  world: '世界'
}

// key 是 number,value 是 string(數(shù)組索引)
interface NumberIndex {
  [index: number]: string
}

const arr: NumberIndex = ['a', 'b', 'c']
console.log(arr[0])  // 'a'

3. 索引簽名 + 具體屬性

interface User {
  name: string
  age: number
  [key: string]: string | number  // 其他屬性只能是 string 或 number
}

const user: User = {
  name: '張三',
  age: 18,
  nickname: '小張',   // 符合 string | number
  score: 90,           // 符合 string | number
  // isActive: true    // boolean 不符合
}

4. 實(shí)際應(yīng)用

// 1. 環(huán)境變量
interface Env {
  [key: string]: string | undefined
  NODE_ENV: 'development' | 'production'
  PORT: string
}

// 2. 錯(cuò)誤消息
interface ErrorMessages {
  [errorCode: string]: string
}

const errors: ErrorMessages = {
  '400': '請求參數(shù)錯(cuò)誤',
  '401': '未授權(quán)',
  '404': '資源不存在'
}

4.4 接口繼承

復(fù)用接口

1. 為什么要繼承?

避免重復(fù)定義相同的屬性。

interface Animal {
  name: string
  age: number
}

// 不繼承:重復(fù)寫相同屬性
interface Dog {
  name: string
  age: number
  breed: string
}

// 用繼承:復(fù)用 Animal 的屬性
interface Dog extends Animal {
  breed: string
}

const myDog: Dog = {
  name: '旺財(cái)',
  age: 3,
  breed: '金毛'
}

2. 多接口繼承

interface HasName {
  name: string
}

interface HasAge {
  age: number
}

interface HasEmail {
  email: string
}

// 繼承多個(gè)接口
interface User extends HasName, HasAge, HasEmail {
  id: number
}

const user: User = {
  id: 1,
  name: '張三',
  age: 18,
  email: 'zhang@example.com'
}

3. 接口繼承接口

interface ButtonProps {
  text: string
  onClick: () => void
}

interface PrimaryButtonProps extends ButtonProps {
  variant: 'primary'
  size: 'large' | 'small'
}

const btn: PrimaryButtonProps = {
  text: '提交',
  onClick: () => {},
  variant: 'primary',
  size: 'large'
}

4. 接口繼承類

了解即可

class User {
  name: string = ''
  age: number = 0
}

// 接口可以繼承類,得到類的所有屬性
interface Admin extends User {
  role: string
}

const admin: Admin = {
  name: '管理員',
  age: 30,
  role: 'admin'
}

4.5 類與接口的區(qū)別

1. 類 Class

類是創(chuàng)建對象的藍(lán)圖,包含屬性和方法的具體實(shí)現(xiàn)。

class Animal {
  name: string
  
  constructor(name: string) {
    this.name = name
  }
  
  speak(): void {
    console.log(`${this.name}發(fā)出聲音`)
  }
}

const dog = new Animal('旺財(cái)')
dog.speak()  // 實(shí)際執(zhí)行代碼

2. 接口 Interface

接口只定義形狀,不包含實(shí)現(xiàn)。

interface Animal {
  name: string
  speak(): void
}

// 類可以實(shí)現(xiàn)接口
class Dog implements Animal {
  name: string
  
  constructor(name: string) {
    this.name = name
  }
  
  speak(): void {
    console.log(`${this.name}汪汪叫`)
  }
}

3. 關(guān)鍵區(qū)別

對比類(Class)接口(Interface)
有沒有實(shí)現(xiàn)只有聲明
能不能 new可以不能
編譯后存在(變成 JS 代碼)不存在(編譯后消失)
用途創(chuàng)建對象定義形狀

4. 實(shí)現(xiàn)多個(gè)接口

interface Flyable {
  fly(): void
}

interface Swimmable {
  swim(): void
}

// 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口
class Duck implements Flyable, Swimmable {
  fly(): void {
    console.log('鴨子飛')
  }
  
  swim(): void {
    console.log('鴨子游泳')
  }
}

4.6 類型別名 vs 接口

1. 對比表格

對比類型別名(type)接口(interface)
定義對象可以可以
定義原始類型可以(type Name = string不能
定義聯(lián)合類型可以(type ID = string | number不能
定義元組可以不能
繼承/擴(kuò)展&(交叉類型)extends
同名合并報(bào)錯(cuò)自動(dòng)合并
推薦用于復(fù)雜類型、聯(lián)合類型、工具類型對象形狀、類實(shí)現(xiàn)

2. 什么時(shí)候用 type?

// 1. 聯(lián)合類型
type ID = string | number
type Status = 'pending' | 'success' | 'error'

// 2. 原始類型別名
type UserName = string

// 3. 元組
type Point = [number, number]

// 4. 工具類型
type Nullable<T> = T | null
type Readonly<T> = { readonly [P in keyof T]: T[P] }

3. 什么時(shí)候用 interface?

// 1. 定義對象形狀(推薦)
interface User {
  name: string
  age: number
}

// 2. 類實(shí)現(xiàn)
interface Runnable {
  run(): void
}
class Dog implements Runnable { ... }

// 3. 需要擴(kuò)展時(shí)
interface Animal {
  name: string
}
interface Dog extends Animal {
  breed: string
}

// 4. 需要同名合并時(shí)(比如給第三方庫補(bǔ)充類型)
interface Window {
  myCustomProperty: string
}

4. 實(shí)際項(xiàng)目中的選擇

// 大多數(shù)情況用 interface(更標(biāo)準(zhǔn))
interface Product {
  id: number
  name: string
  price: number
}

// 聯(lián)合類型用 type
type ProductStatus = 'inStock' | 'outOfStock' | 'discontinued'

// 組合時(shí)用 type
type ProductWithStatus = Product & { status: ProductStatus }

5. 泛型

5.1 為什么需要泛型?

1. 一個(gè)重復(fù)代碼的問題

假設(shè)你要寫一個(gè)函數(shù),返回傳入的參數(shù):

// 只能處理 number
function identityNumber(value: number): number {
  return value
}

// 只能處理 string
function identityString(value: string): string {
  return value
}

// 只能處理 boolean
function identityBoolean(value: boolean): boolean {
  return value
}

三個(gè)函數(shù)邏輯完全一樣,只是類型不同。能不能寫一個(gè)函數(shù)搞定?

2. 用 any 的問題

function identityAny(value: any): any {
  return value
}

const result = identityAny('hello')
result.toUpperCase()  // 運(yùn)行時(shí)沒問題,但 TS 不知道 result 是 string
result.toFixed()      // 運(yùn)行時(shí)報(bào)錯(cuò),但 TS 不阻止

 any讓你失去了類型保護(hù)。

3. 用聯(lián)合類型的問題

function identityUnion(value: string | number): string | number {
  return value
}

const result = identityUnion('hello')
// result 類型是 string | number,不能直接調(diào)用 toUpperCase()
result.toUpperCase()  // 報(bào)錯(cuò):可能不是 string

4. 泛型的解決方案

// <T> 是一個(gè)類型變量,調(diào)用時(shí)確定
function identity<T>(value: T): T {
  return value
}

const num = identity(100)     // num 類型是 number
const str = identity('hello') // str 類型是 string
const bool = identity(true)   // bool 類型是 boolean

// 調(diào)用時(shí)不用寫類型,TS 自動(dòng)推斷
const result = identity('hello')
result.toUpperCase()  // TS 知道是 string

5.2 泛型函數(shù)

1. 基本寫法

// 函數(shù)聲明
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0]
}

// 箭頭函數(shù)
const getLast = <T>(arr: T[]): T | undefined => {
  return arr[arr.length - 1]
}

// 使用
const numbers = [1, 2, 3, 4, 5]
const firstNum = getFirst(numbers)   // 類型是 number | undefined

const strings = ['a', 'b', 'c']
const lastStr = getLast(strings)     // 類型是 string | undefined

2. 多個(gè)泛型參數(shù)

// 交換數(shù)組中的兩個(gè)元素
function swap<T, U>(arr: [T, U]): [U, T] {
  return [arr[1], arr[0]]
}

const swapped = swap([1, 'hello'])  // 類型是 [string, number]
console.log(swapped)  // ['hello', 1]

3. 在實(shí)際項(xiàng)目中的應(yīng)用

// 1. 封裝 API 請求
async function fetchApi<T>(url: string): Promise<T> {
  const response = await fetch(url)
  return response.json()
}

interface User {
  id: number
  name: string
}

interface Product {
  id: number
  title: string
  price: number
}

const user = await fetchApi<User>('/api/users/1')
const product = await fetchApi<Product>('/api/products/1')

// 2. 類型安全的 localStorage
function getStorage<T>(key: string): T | null {
  const value = localStorage.getItem(key)
  if (value === null) return null
  return JSON.parse(value) as T
}

function setStorage<T>(key: string, value: T): void {
  localStorage.setItem(key, JSON.stringify(value))
}

interface UserSettings {
  theme: 'light' | 'dark'
  fontSize: number
}

const settings = getStorage<UserSettings>('settings')

5.3 泛型接口

1. 基本寫法

// 泛型接口
interface Box<T> {
  value: T
  getValue(): T
}

// 使用
const numberBox: Box<number> = {
  value: 123,
  getValue() {
    return this.value
  }
}

const stringBox: Box<string> = {
  value: 'hello',
  getValue() {
    return this.value
  }
}

2. API 響應(yīng)接口(實(shí)際應(yīng)用)

// 通用的 API 響應(yīng)格式
interface ApiResponse<T> {
  code: number
  data: T
  message: string
  timestamp: number
}

// 不同接口返回不同 data 類型
interface User {
  id: number
  name: string
}

interface Product {
  id: number
  title: string
  price: number
}

// 使用
const userResponse: ApiResponse<User> = {
  code: 200,
  data: { id: 1, name: '張三' },
  message: 'success',
  timestamp: Date.now()
}

const productResponse: ApiResponse<Product[]> = {
  code: 200,
  data: [{ id: 1, title: '商品', price: 99 }],
  message: 'success',
  timestamp: Date.now()
}

3. 分頁接口

interface PageResult<T> {
  data: T[]
  total: number
  page: number
  pageSize: number
  hasNext: boolean
}

async function getUsers(page: number): Promise<PageResult<User>> {
  const response = await fetch(`/api/users?page=${page}`)
  return response.json()
}

5.4 泛型類

1. 基本寫法

// 泛型類
class Stack<T> {
  private items: T[] = []
  
  push(item: T): void {
    this.items.push(item)
  }
  
  pop(): T | undefined {
    return this.items.pop()
  }
  
  peek(): T | undefined {
    return this.items[this.items.length - 1]
  }
  
  isEmpty(): boolean {
    return this.items.length === 0
  }
}

// 使用
const numberStack = new Stack<number>()
numberStack.push(1)
numberStack.push(2)
numberStack.push(3)
console.log(numberStack.pop())  // 3

const stringStack = new Stack<string>()
stringStack.push('a')
stringStack.push('b')
console.log(stringStack.pop())  // 'b'

2. 實(shí)際應(yīng)用:緩存類

interface CacheItem<T> {
  value: T
  expireAt: number
}

class Cache<T> {
  private cache: Map<string, CacheItem<T>> = new Map()
  
  set(key: string, value: T, ttl: number = 60000): void {
    this.cache.set(key, {
      value,
      expireAt: Date.now() + ttl
    })
  }
  
  get(key: string): T | null {
    const item = this.cache.get(key)
    if (!item) return null
    
    if (Date.now() > item.expireAt) {
      this.cache.delete(key)
      return null
    }
    
    return item.value
  }
  
  delete(key: string): void {
    this.cache.delete(key)
  }
  
  clear(): void {
    this.cache.clear()
  }
}

// 使用
const userCache = new Cache<User>()
userCache.set('user:1', { id: 1, name: '張三' }, 30000)

const cachedUser = userCache.get('user:1')

5.5 泛型約束(限制泛型的范圍)

1. 為什么要約束?

// 不加約束:T 可以是任何類型
function getLength<T>(value: T): number {
  return value.length  // 報(bào)錯(cuò):T 可能沒有 length 屬性
}

2. 用 extends 添加約束

// 約束 T 必須有 length 屬性
interface HasLength {
  length: number
}

function getLength<T extends HasLength>(value: T): number {
  return value.length  // 可以
}

getLength('hello')     // 5(string 有 length)
getLength([1, 2, 3])   // 3(數(shù)組有 length)
getLength({ length: 10 })  // 10
getLength(123)         // 報(bào)錯(cuò):number 沒有 length

3. 多種約束

// 約束 T 必須有 name 和 age
interface HasNameAndAge {
  name: string
  age: number
}

function greet<T extends HasNameAndAge>(user: T): string {
  return `你好,${user.name}!你今年${user.age}歲`
}

greet({ name: '張三', age: 18 })  
greet({ name: '李四', age: 20, city: '北京' })  // 可以有額外屬性
greet({ name: '王五' })  // 缺少 age

4. 約束為某個(gè)類的實(shí)例

class Animal {
  name: string = ''
}

// 約束 T 必須是 Animal 的子類
function createInstance<T extends Animal>(Ctor: new () => T): T {
  return new Ctor()
}

class Dog extends Animal {
  breed: string = ''
}

const dog = createInstance(Dog)  // dog 類型是 Dog

5.6 多個(gè)泛型參數(shù)

1. 基本用法

// 合并兩個(gè)對象
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 }
}

const result = merge({ name: '張三' }, { age: 18 })
// result 類型是 { name: string } & { age: number }
console.log(result.name, result.age)

// 鍵值對
class KeyValuePair<K, V> {
  constructor(public key: K, public value: V) {}
}

const pair = new KeyValuePair<string, number>('age', 18)
console.log(pair.key, pair.value)

2. 泛型約束 + 多個(gè)參數(shù)

// 約束 T 和 U 必須有共同的 key
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = { name: '張三', age: 18, city: '北京' }
const name = getProperty(user, 'name')  // 類型是 string
const age = getProperty(user, 'age')    // 類型是 number
// const invalid = getProperty(user, 'invalid')  // 報(bào)錯(cuò)

5.7 內(nèi)置泛型工具

Partial、Pick、Omit、Record

1. 所有屬性變成可選 Partial

interface User {
  id: number
  name: string
  age: number
  email: string
}

// 所有屬性都變成可選
type PartialUser = Partial<User>
// 等價(jià)于 { id?: number; name?: string; age?: number; email?: string }

// 適用場景:更新用戶(只傳要改的字段)
function updateUser(id: number, updates: Partial<User>): void {
  // 只更新傳進(jìn)來的字段
}
updateUser(1, { name: '新名字' })  // 只傳 name
updateUser(1, { age: 19 })        // 只傳 age

2. 挑選部分屬性 Pick

interface User {
  id: number
  name: string
  age: number
  email: string
  password: string
}

// 只挑出需要的屬性
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// 等價(jià)于 { id: number; name: string; email: string }

// 適用場景:返回給前端的數(shù)據(jù)(不包含密碼)
function getUser(): PublicUser {
  return {
    id: 1,
    name: '張三',
    email: 'zhang@example.com'
  }
}

3. 排除部分屬性 Omit

interface User {
  id: number
  name: string
  age: number
  email: string
  password: string
}

// 排除密碼屬性
type PublicUser = Omit<User, 'password'>
// 等價(jià)于 { id: number; name: string; age: number; email: string }

// 排除多個(gè)屬性
type UserWithoutPasswordAndAge = Omit<User, 'password' | 'age'>

// 適用場景:創(chuàng)建用戶(不需要 id)
type CreateUser = Omit<User, 'id'>

4. 創(chuàng)建鍵值對類型 Record

// 定義對象,key 是 string,value 是 number
type ScoreMap = Record<string, number>
const scores: ScoreMap = {
  math: 90,
  english: 85,
  chinese: 92
}

// 定義 key 只能是特定值
type Status = 'pending' | 'success' | 'error'
type StatusConfig = Record<Status, { color: string; text: string }>

const config: StatusConfig = {
  pending: { color: 'orange', text: '處理中' },
  success: { color: 'green', text: '成功' },
  error: { color: 'red', text: '失敗' }
}

// 定義 key 是數(shù)字,value 是字符串
type WeekdayMap = Record<1 | 2 | 3 | 4 | 5 | 6 | 7, string>
const weekdays: WeekdayMap = {
  1: '周一',
  2: '周二',
  3: '周三',
  4: '周四',
  5: '周五',
  6: '周六',
  7: '周日'
}

5. 內(nèi)置泛型工具速查表

工具作用示例
Partial<T>所有屬性變可選Partial<User>
Required<T>所有屬性變必選Required<PartialUser>
Readonly<T>所有屬性變只讀Readonly<User>
Pick<T, K>挑選部分屬性Pick<User, 'id' | 'name'>
Omit<T, K>排除部分屬性Omit<User, 'password'>
Record<K, T>創(chuàng)建鍵值對類型Record<string, number>
Exclude<T, U>排除聯(lián)合類型中的某些Exclude<'a' | 'b' | 'c', 'a'>
Extract<T, U>提取聯(lián)合類型中的某些Extract<'a' | 'b' | 'c', 'a' | 'b'>
NonNullable<T>排除 null 和 undefinedNonNullable<string | null>
ReturnType<T>獲取函數(shù)返回值類型ReturnType<typeof fn>
Parameters<T>獲取函數(shù)參數(shù)類型Parameters<typeof fn>

6. 類型操作

6.1 獲取對象的鍵 keyof

1. 基本用法

keyof 獲取對象類型的所有鍵,組成聯(lián)合類型。

interface User {
  id: number
  name: string
  age: number
  email: string
}

type UserKeys = keyof User  // "id" | "name" | "age" | "email"

let key: UserKeys = 'id'    
key = 'name'                
key = 'address'             // 報(bào)錯(cuò)

2. 實(shí)際應(yīng)用:類型安全的屬性訪問

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = { name: '張三', age: 18, city: '北京' }

const name = getProperty(user, 'name')  // 類型是 string
const age = getProperty(user, 'age')    // 類型是 number
// const invalid = getProperty(user, 'invalid')  // 報(bào)錯(cuò)

function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value
}

setProperty(user, 'name', '李四')  
setProperty(user, 'age', '二十')    // 報(bào)錯(cuò):不能把 string 賦給 number

3. 遍歷對象的鍵

function printObject<T>(obj: T): void {
  for (let key in obj) {
    // 需要類型斷言,因?yàn)?key 是 string,不一定是 keyof T
    console.log(`${key}: ${obj[key as keyof T]}`)
  }
}

6.2 獲取變量的類型 typeof

1. 基本用法

typeof 在 TypeScript 中獲取變量的類型(不是運(yùn)行時(shí)的值)。

const user = {
  name: '張三',
  age: 18,
  city: '北京'
}

// 獲取 user 的類型
type User = typeof user
// 等價(jià)于 { name: string; age: number; city: string }

const anotherUser: User = {
  name: '李四',
  age: 20,
  city: '上海'
}

2. 實(shí)際應(yīng)用:避免重復(fù)定義類型

// 不用 typeof:重復(fù)定義
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retry: 3
}

interface Config {
  apiUrl: string
  timeout: number
  retry: number
}

// 用 typeof:自動(dòng)推導(dǎo)
type Config = typeof config

// 在函數(shù)中使用
function initApp(config: typeof config) {
  console.log(`API: ${config.apiUrl}`)
}

3. 獲取函數(shù)類型

function add(a: number, b: number): number {
  return a + b
}

type AddFunction = typeof add
// 等價(jià)于 (a: number, b: number) => number

const multiply: AddFunction = (a, b) => a * b

4. 與 keyof 結(jié)合

const user = {
  name: '張三',
  age: 18,
  address: {
    city: '北京',
    street: '長安街'
  }
}

type User = typeof user
type UserKeys = keyof User  // "name" | "age" | "address"

6.3 索引訪問類型

1. 基本用法

用 T[K] 獲取類型 T 中鍵 K 的類型。

interface User {
  id: number
  name: string
  age: number
  address: {
    city: string
    street: string
  }
}

type IdType = User['id']        // number
type NameType = User['name']    // string
type AddressType = User['address']  // { city: string; street: string }
type CityType = User['address']['city']  // string

2. 聯(lián)合類型的索引訪問

interface User {
  id: number
  name: string
  age: number
}

// 獲取多個(gè)屬性的類型(聯(lián)合類型)
type IdOrName = User['id' | 'name']  // number | string

// 獲取所有值的類型
type UserValues = User[keyof User]  // number | string

3. 數(shù)組元素的類型

type StringArray = string[]
type ItemType = StringArray[0]  // string
type ItemType2 = StringArray[number]  // string

// 實(shí)際應(yīng)用:獲取數(shù)組元素的類型
const users = [
  { id: 1, name: '張三' },
  { id: 2, name: '李四' }
]

type User = typeof users[0]  // { id: number; name: string }
type User2 = typeof users[number]  // { id: number; name: string }

4. 提取函數(shù)返回值類型

function fetchUser(): Promise<{ id: number; name: string }> {
  return Promise.resolve({ id: 1, name: '張三' })
}

// 提取 Promise 里的類型
type User = Awaited<ReturnType<typeof fetchUser>>
// 等價(jià)于 { id: number; name: string }

6.4 條件類型

1. 基本用法

T extends U ? X : Y:如果 T 可以賦值給 U,則類型是 X,否則是 Y。

type IsString<T> = T extends string ? true : false

type A = IsString<string>   // true
type B = IsString<number>   // false
type C = IsString<'hello'>  // true('hello' 是 string 的子類型)

2. 實(shí)際應(yīng)用:排除 null 和 undefined

type NonNullable<T> = T extends null | undefined ? never : T

type A = NonNullable<string | null>        // string
type B = NonNullable<string | undefined>   // string
type C = NonNullable<string | null | undefined>  // string

3. 實(shí)際應(yīng)用:提取函數(shù)返回值類型

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function add(a: number, b: number): number {
  return a + b
}

type AddReturn = ReturnType<typeof add>  // number

4. 分布式條件類型

type ToArray<T> = T extends any ? T[] : never

type A = ToArray<string | number>
// 等價(jià)于 string[] | number[](不是 (string | number)[])

6.5 映射類型

1. 基本用法

映射類型可以基于舊類型創(chuàng)建新類型。

// 把所有屬性變成只讀
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

// 把所有屬性變成可選
type Partial<T> = {
  [P in keyof T]?: T[P]
}

// 使用
interface User {
  id: number
  name: string
  age: number
}

type ReadonlyUser = Readonly<User>
// { readonly id: number; readonly name: string; readonly age: number }

type PartialUser = Partial<User>
// { id?: number; name?: string; age?: number }

2. 自定義映射類型

// 把所有屬性變成 null 可選
type Nullable<T> = {
  [P in keyof T]: T[P] | null
}

// 把所有屬性變成字符串
type Stringify<T> = {
  [P in keyof T]: string
}

interface Product {
  id: number
  name: string
  price: number
}

type NullableProduct = Nullable<Product>
// { id: number | null; name: string | null; price: number | null }

type StringProduct = Stringify<Product>
// { id: string; name: string; price: string }

3. 修改屬性名

// 給屬性名加 get 前綴
type Getters<T> = {
  [P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
}

interface User {
  name: string
  age: number
}

type UserGetters = Getters<User>
// { getName: () => string; getAge: () => number }

6.6 類型斷言

告訴 TS:相信我

1. 什么是類型斷言?

當(dāng) TypeScript 無法推斷類型,但你知道更準(zhǔn)確的類型時(shí),可以「斷言」。

// as 語法
const value: any = 'hello'
const length = (value as string).length

// 尖括號語法(在 JSX 中不能用)
const length2 = (<string>value).length

2. 常見使用場景

// 1. 處理 DOM 元素
const input = document.getElementById('username') as HTMLInputElement
console.log(input.value)  // TS 知道這是 input 元素

// 2. 處理 any 類型
const data: any = { name: '張三', age: 18 }
const name = (data as { name: string }).name

// 3. 處理聯(lián)合類型
function getLength(value: string | number): number {
  // 告訴 TS:這里 value 一定是 string
  return (value as string).length
}

3. 雙重?cái)嘌裕ㄎkU(xiǎn))

const value: string = 'hello'
// 直接斷言成 number 會(huì)報(bào)錯(cuò)
// const num = value as number  // 報(bào)錯(cuò)

// 雙重?cái)嘌裕合绒D(zhuǎn)成 any,再轉(zhuǎn)成目標(biāo)類型(危險(xiǎn),不推薦)
const num = value as any as number  // 可能有問題

4. 非空斷言 !

當(dāng)你確定某個(gè)值不是 null 或 undefined 時(shí),可以用 !

interface User {
  name?: string
}

function getUserName(user: User): string {
  // 告訴 TS:user.name 一定存在
  return user.name!  // 如果實(shí)際是 undefined,運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
}

// DOM 元素
const element = document.getElementById('app')!
element.innerHTML = 'Hello'

5. 類型斷言 vs 類型轉(zhuǎn)換

對比類型斷言類型轉(zhuǎn)換
運(yùn)行時(shí)不影響會(huì)轉(zhuǎn)換值
語法as stringString(value)
作用告訴 TS 類型實(shí)際轉(zhuǎn)換值
// 類型斷言:不轉(zhuǎn)換值,只告訴 TS
const num = 123
const str = num as any as string  // 運(yùn)行時(shí) str 還是 123(數(shù)字?。?

// 類型轉(zhuǎn)換:實(shí)際轉(zhuǎn)換值
const realStr = String(num)  // 運(yùn)行時(shí) realStr 是 "123"

恭喜你完成了TS的學(xué)習(xí),明天還有一篇實(shí)戰(zhàn)!

總結(jié)

到此這篇關(guān)于TypeScript前端的必修課之從JS到TS的文章就介紹到這了,更多相關(guān)TS前端必修課內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS實(shí)現(xiàn)數(shù)組去重的常用方法

    JS實(shí)現(xiàn)數(shù)組去重的常用方法

    在JavaScript開發(fā)中,數(shù)組操作是不可或缺的一部分,數(shù)組去重作為數(shù)據(jù)處理的一個(gè)常見需求,旨在從一個(gè)可能包含重復(fù)元素的數(shù)組中移除重復(fù)項(xiàng),只保留唯一值,本文將詳細(xì)介紹幾種常見的數(shù)組去重方法,并結(jié)合實(shí)際應(yīng)用場景進(jìn)行探討,需要的朋友可以參考下
    2024-12-12
  • ionic由于使用了header和subheader導(dǎo)致被遮擋的問題的兩種解決方法

    ionic由于使用了header和subheader導(dǎo)致被遮擋的問題的兩種解決方法

    這篇文章主要介紹了ionic由于使用了header和subheader導(dǎo)致被遮擋的問題的兩種解決方法,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • JavaScript canvas實(shí)現(xiàn)流星特效

    JavaScript canvas實(shí)現(xiàn)流星特效

    這篇文章主要為大家詳細(xì)介紹了JavaScript canvas實(shí)現(xiàn)流星特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • js獲取url參數(shù)的使用擴(kuò)展實(shí)例

    js獲取url參數(shù)的使用擴(kuò)展實(shí)例

    js獲取參數(shù)的具體應(yīng)用,歡迎加入腳本之家交流群
    2007-12-12
  • Bootstrap的Refresh Icon也spin起來

    Bootstrap的Refresh Icon也spin起來

    本文通過實(shí)例給大家介紹Bootstrap的Refresh Icon相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧
    2016-07-07
  • 淺談Layui的eleTree樹式選擇器使用方法

    淺談Layui的eleTree樹式選擇器使用方法

    今天小編就為大家分享一篇淺談Layui的eleTree樹式選擇器使用方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • javascript prototype原型操作筆記

    javascript prototype原型操作筆記

    在 JavaScript 中,prototype 不但能讓對象共享自己財(cái)富,而且 prototype 還有尋根問祖的天性,從而使得先輩們的遺產(chǎn)可以代代相傳。
    2009-12-12
  • javascript設(shè)計(jì)模式之解釋器模式詳解

    javascript設(shè)計(jì)模式之解釋器模式詳解

    這篇文章主要介紹了javascript設(shè)計(jì)模式之解釋器模式詳解,當(dāng)有一個(gè)語言需要解釋執(zhí)行,并且可以將該語言中的句子表示為一個(gè)抽象語法樹的時(shí)候,可以考慮使用解釋器模式,需要的朋友可以參考下
    2014-06-06
  • 三種帶箭頭提示框總結(jié)實(shí)例

    三種帶箭頭提示框總結(jié)實(shí)例

    這篇文章主要介紹了三種帶箭頭提示框總結(jié)實(shí)例 的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • JavaScript實(shí)現(xiàn)下拉列表選擇框

    JavaScript實(shí)現(xiàn)下拉列表選擇框

    這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)下拉列表選擇框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10

最新評論

崇文区| 康平县| 余江县| 于都县| 濮阳市| 高州市| 杨浦区| 乌兰浩特市| 龙里县| 麻栗坡县| 和田县| 崇文区| 定远县| 紫云| 灵台县| 远安县| 综艺| 建平县| 敦化市| 绥阳县| 平定县| 吉木乃县| 维西| 平泉县| 乐东| 手游| 前郭尔| 河间市| 米林县| 汶川县| 绵阳市| 西和县| 靖边县| 大方县| 万全县| 宣汉县| 阳信县| 扶风县| 库伦旗| 大渡口区| 郓城县|