與TypeScript的初次相遇

前言

我始終相信,任何新技術(shù)的出現(xiàn)都是為了解決原有技術(shù)的某些痛點(diǎn)。 毫無疑問,JavaScript是一門優(yōu)秀且強(qiáng)大的語言。但是由于其歷史背景的原因,Brendan Eich的設(shè)計存在著不少的缺點(diǎn),但不可否認(rèn)JavaScript也在一點(diǎn)點(diǎn)變好,比如ES5以后推出的 let、const、class、Promise、async/await 等等。

而直至今日J(rèn)avaScript依舊沒有加入的類型檢測機(jī)制,實(shí)際上就是讓我們在開發(fā)代碼的時候讓 錯誤出現(xiàn)提前。其實(shí)開發(fā)中需要有一個共識,那就是:

  • 能在寫代碼期間發(fā)現(xiàn)錯誤就不要再代碼編譯期間發(fā)現(xiàn)

  • 能在代碼編譯期間發(fā)現(xiàn)錯誤就不要在代碼運(yùn)行期間發(fā)現(xiàn)

  • 能在開發(fā)期間發(fā)現(xiàn)錯誤就不要在測試期間發(fā)現(xiàn)

  • 能在測試期間發(fā)現(xiàn)錯誤就不要在上線期間發(fā)現(xiàn)

TypeScript的優(yōu)勢就是可以做到 類型校驗,能夠在代碼編寫期間就直接報錯,這是JavaScript做不到的。

在2014年Facebook也推出過flow來對JavaScript進(jìn)行類型檢測,Vue2.x就是采用的這種方式來進(jìn)行類型檢查。而現(xiàn)在TypeScript已經(jīng)完全碾壓flow,絕大部分需要類型檢測的項目都會優(yōu)先去選擇TypeScript,學(xué)習(xí)它不僅僅可以為我們的代碼增加類型約束,而且可以培養(yǎng)我們前端程序員也具備類型思維。


TypeScript編譯環(huán)境

我們要知道,瀏覽器是無法直接編譯運(yùn)行TypeScript的。只有通過TypeScript環(huán)境將TS代碼轉(zhuǎn)換成JS代碼才能正常運(yùn)行,所以我們需要先安裝一下TypeScript環(huán)境。

npm install typescript -g

安裝完成后,我們可以通過tsc --version查看它的版本號,然后去編寫下方代碼吧

// HelloTypeScript.ts
let message: string = "Hello World"
console.log(message)
export {}

寫完之后又有了另外一個問題,我該怎么運(yùn)行?直接引入TS文件嗎,瀏覽器無法解析呀。

這時候就需要用到上面安裝的tsc了,我們在終端輸入tsc HelloTypeScript.ts并回車,會發(fā)現(xiàn)文件夾內(nèi)編譯除了HelloTypeScript.js文件,我們在html中引入該文件就可以執(zhí)行了。

編譯后的代碼.jpg

當(dāng)然我們發(fā)現(xiàn)這種方式運(yùn)行ts代碼會比較繁瑣,那么我們有兩種解決方案 webpack/vite環(huán)境 與 ts-node。先介紹一下 ts-node,首先我們也需要進(jìn)行安裝:

npm install ts-node -g  // 安裝ts-node
npm install tslib @types/node -g  // 安裝ts-node的依賴包

安裝完成后我們就可以直接通過ts-node main.ts運(yùn)行TypeScript代碼了


TypeScript基本語法

變量聲明

經(jīng)過上述示例我們也知道了在TypeScript中定義變量需要指定 標(biāo)識符 的類型,
聲明類型后TypeScript會進(jìn)行類型檢測,聲明的類型可以被稱為\color{red} {類型注解}。
所以一個完整的聲明格式是:var/let/const 標(biāo)識符: 類型注解 = 賦值;

類型推導(dǎo)

定義變量是直接進(jìn)行賦值就會進(jìn)行 類型推導(dǎo),也就是如果不手動設(shè)置就會默認(rèn)給變量設(shè)置個類型
但是let、const有所不同:

  • let定義的變量,推導(dǎo)出來的是通用類型 [ string, number ... ]
  • const定義的變量推導(dǎo)出來的是字面量類型

數(shù)組注解方式

注意事項:在開發(fā)中數(shù)組通常存放相同類型數(shù)據(jù),切記

數(shù)組的類型注解方式有兩種

  • 1. string[]: 數(shù)組類型,并且數(shù)組中存放的字符串類型
  • 2. Array<string>: 數(shù)組類型,通過泛型實(shí)現(xiàn)上面的效果
let names: string[] = ['aaa', 'bbb', 'ccc']
let nums: Array<number> = [111, 222, 333]

在開發(fā)中我們通常采用第一種方式,只有特殊情況下才會采用泛型數(shù)組寫法

對象的注解方式

對象的注解方式還可以通過type和interface定義,這里先說下最簡單的直接定義的方式

const myInfo: {
    name: string,
    age: number,
    height: number   // 因為是必傳參數(shù),所以這個對象其實(shí)是報錯的
} = {
    name: 'www',
    age: 18
}

如果不想這樣編寫,其實(shí)可以跟js一樣的直接定義,因為會進(jìn)行類型推導(dǎo)。再補(bǔ)充一個小知識點(diǎn),如果我們給一個對象定義類型為object,那么只能證明它是一個對象,但是不可以獲取和設(shè)置任何數(shù)據(jù),所以千萬不要直接給一個對象設(shè)置object類型,如果嫌麻煩可以設(shè)置any類型。

null、undefined與symbol

null、undefined這兩個類型和它們的值 是一樣的,意味著是他們的值也是它們的類型

let n: null = null
let u: undefined = undefined

symbol則是通過Symbol定義相同名稱

const s1: symbol = Symbol("title")
const s2: symbol = Symbol("title")

const person = {
    [s1]: "張三",
    [s2]: "王五"
}

函數(shù)的注解方式

函數(shù)中的形參都是外面?zhèn)鞯?,所以并不能進(jìn)行類型推導(dǎo)。一般情況下都是明確指定類型的

function sum(n1: number, n2: number) {
    return n1 + n2
}

但是函數(shù)的返回值是可以通過類型推導(dǎo)出來的,也是可以明確指定

function str(s1: string, s2: string): string {
    return s1 + s2
}

匿名函數(shù)調(diào)用時最好不要添加類型注解,因為它本身就會根據(jù)上下文添加類型注解,如:

const names = ['aaa', 'bbb', 'ccc']
// 這里通過類型推導(dǎo)能夠知道這個數(shù)組里面都是字符串

// 其實(shí)已經(jīng)推導(dǎo)出來了,不需要再手動定義
// - item => string
// - index => number
// - arr => string[]
names.forEach((item, index, arr) => {
    console.log(item.toUpperCase())
})

如果函數(shù)的形參是一個對象的話我們也可以進(jìn)行注解

function pointzzzz(point: { x: number, y: number }) {
    console.log(point.x)
    console.log(point.y)
}

如果覺得太長了話也可以抽一個type

type PointType = { x: number, y: number }
function pointzzzz(point: PointType) {
    console.log(point.x)
    console.log(point.y)
}

TypeScript類型擴(kuò)展

any類型

它是ts中比較重要的類型,意思是不限制標(biāo)識符的任意類型,并且可以在標(biāo)識符上進(jìn)行任意操作,也就是相當(dāng)于回到了JavaScript定義標(biāo)識符。
但是并不推薦濫用,否則就會變成anyscript。只有在某些特定情況下我推薦使用any類型,比如如下幾種:

  • 從服務(wù)器拿到的數(shù)據(jù) 極其繁瑣,難以整理,詞條巨多并無法確定類型
  • 引入第三方庫我們?nèi)鄙賹?yīng)的類型注解,我們也可以使用any

unknown類型

與any類型看上去是很相似的,都是無法確定類型,但是不同的是 any類型變量做任何操作都是可以的,但是unknown不可以!也就是不可以直接取屬性、調(diào)方法等等

但是如果確實(shí)需要進(jìn)行操作,那么就需要進(jìn)行類型縮小,也就是類型校驗后并進(jìn)行對應(yīng)操作

let foo: any = "aaa"
let bar: unknown = 'bbb'

console.log(foo.length)  // 3
console.log(bar.length)  // 代碼直接編輯器報錯

if(typeof bar === 'string') {  // 進(jìn)行類型縮小確定類型
    console.log(bar.lenth)  // 3
}

void類型

  • 在ts中一個函數(shù)如果沒有任何返回值,那么返回值的類型就是void。這個類型主要就是用來指定函數(shù)類型的返回值是void
  • 如果我們定義了返回值void,那么其實(shí)也可以手動返回一個undefined,但是如果定義的是undefined類型那就只能且必須返回undefined
  • 基于上下文類型推導(dǎo)出來的函數(shù)返回值類型為void,不強(qiáng)制返回值類型

never類型

出現(xiàn)場景:
  • 首先,我們在開發(fā)中很少使用never類型,類型推導(dǎo)的時候可能會推導(dǎo)出never類型,基本是以下這些情況

    • 函數(shù)死循環(huán),永遠(yuǎn)不會返回任何東西
    • 返回空數(shù)組
  • 其次,我們在開發(fā)框架工具的時候可能會用到never類型,用來限制協(xié)同合作時可能會出現(xiàn)的錯誤

    • 封裝框架用來提示別人擴(kuò)展工具時有一些提示(對沒處理的地方直接報錯)

    • function foo(message: string | number) {
          switch(typeof message) {
              case "string":
                  console.log(message.length)
              case 'number':
                  console.log(message)
              default:
                  const check: never = message
          }
      }
      
    • 如果別人想給這個函數(shù)擴(kuò)展個boolean類型,直接在形參中添加 | boolean 的話defaule的check變量就會報錯,因為沒有寫case進(jìn)行新類型的處理

    • 尤大解釋never.jpg
  • 最后,在封裝一些類型工具的時候可能會用到never(比如類型體操)

tuple類型

元組類型,多個類型組合在一起TypeScript數(shù)據(jù)類型。
一般在函數(shù)的返回值類型里用的多一些,最經(jīng)典的就是React中的 useState,返回的就是一個元組類型(參數(shù)+函數(shù))

我們會發(fā)現(xiàn)元組類型和數(shù)組類型很像,其實(shí)不然,他們之間還是有比較大的差別的。原來的數(shù)組我們通常只會把同類型數(shù)據(jù)放在一起,否則獲取值后編譯器不能知道明確類型,所以代碼提示會很差。如果碰到需要將多類型數(shù)據(jù)放在一起的需求時通常會使用對象來解決,而ts中則可以通過 元組類型 解決

// 普通數(shù)組存儲
const info: any[] = ['www', 18, 1.99]
const v1 = info[1]  // v1類型為 any

// 元組類型存儲
const infos: [string, number, number] = ['www', 18, 1.88]
const v2 = infos[1]  // v2類型為 number

enum類型

枚舉類型,他會把可能出現(xiàn)的之全都放到一個類型中,并且默認(rèn)一次賦值0,1,2,3....

enum Direction {
    LEFT,
    RIGHT,
    UP,
    DOWN
}
// 上面這種方式等于
enum Direction {
    LEFT = 0,
    RIGHT = 1,
    UP = 2,
    DOWN = 3 
}
// 也可以設(shè)置第一個值后依次遞增
enum Direction {
    LEFT = 100,
    RIGHT,
    UP,
    DOWN
}
// 也可以從中間開始設(shè)置某個值
enum Direction {
    LEFT,
    RIGHT,
    UP = 'TOP',
    DOWN = 'BOTTOM'
}

TypeScript語法細(xì)節(jié)

聯(lián)合類型

它是由兩個或者多個其他類型組成的類型,表示可以是這些類型中任意一個值。聯(lián)合類型中的每一個類型被稱為聯(lián)合成員。
它的使用很簡單,但是通常會與類型縮小一起使用,就像下面這樣:

function sends(id: number | string) {  // 聯(lián)合類型
    if(typeof id === 'number') {  // 類型縮小
        console.log(id)
    } else {
        console.log(id.length)
    }
}

類型別名

type關(guān)鍵字來定義類型別名,可以把過于臃腫的類型抽取出來,這樣就可以使代碼邏輯更清晰

function printABC(info: { a: number, b: string, c: string[] }) {
    console.log(info.a, info.b, info.c)
}

// 可以抽取成這樣
type infoType = { 
    a: number,
    b: string,
    c: string[] 
}
function printABC(info: infoType) {
    console.log(info.a, info.b, info.c)
}

接口聲明

通過 interface關(guān)鍵字聲明接口類型,和type類型別名很像,大多數(shù)情況下兩個都可以使用。在接口中幾乎所有的特性都可以在type中使用

兩個關(guān)鍵字的主要區(qū)別:

  • type類型適用范圍更廣,接口類型只能夠聲明對象
  • 聲明對象時type不可以多次聲明, interface可以,所以對象類型中接口更加靈活
  • interface可以實(shí)現(xiàn)繼承
  • 接口不僅僅可以繼承,它也可以通過關(guān)鍵字implements實(shí)現(xiàn)的。實(shí)現(xiàn)接口則是更加方便我們?nèi)?chuàng)建實(shí)例對象

所以一般聲明對象類型就使用interface,聲明其他類型就使用type

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

interface IKun extends Ipersion {
    height: number
}

// 三個參數(shù)一個都不能少,因為是繼承了
const heizi: IKun = {
    name: "xx",
    age: 44,
    height: 1.78 
}

// 實(shí)現(xiàn)接口
class nPerson implements IKun {
  name: string
  age: number
  height: number
}

交叉類型

交叉類型是需要同時滿足類型A和類型B兩種類型,是通過 & 來定義的。通常是對象才會使用到交叉類型

interface IKun { 
    name: string,
    age: number
}

interface ICoder {
    coding: () => void
}

const p1: IKun & ICoder = {  // 需要同時滿足兩個接口的屬性
    name: 'www',
    age: 16,
    coding: function() {
        console.log("coder")
    }
}

類型斷言

通過關(guān)鍵字as進(jìn)行類型斷言,手動確定我拿到的這個元素它是什么類型

它可以斷言成一個更加具體的類型,也可以斷言成更加不具體的類型(any, unknown)

// 通過標(biāo)簽選擇器能夠準(zhǔn)確拿到元素的類型,這里就是 HTMLImageElement | null
const imgs = document.querySelector("img")  
// 但是通過類選擇器之類的拿到的元素就很寬泛了,所以這里的類型是 Element | null
const imgss = document.querySelector(".img") 

// 進(jìn)行類型縮小,杜絕null的存在后取值
if(typeof imgs !== "null") {
    console.log(imgs.src)  // 能夠順利取值
}

if(typeof imgss !== 'null') {
    console.log(imgss.src)  // 報錯,因為編譯器不知道imgss是一個圖片元素
}

上面的案例如果我們確定類選擇器拿到的一定是一個圖片元素,那么就可以進(jìn)行類型斷言!

const imgss = document.querySelector(".img") as HTMLImageElement

甚至斷言后都不需要進(jìn)行類型縮小了,可以直接獲取對應(yīng)數(shù)據(jù)

非空類型斷言

告訴編輯器我這個參數(shù)一定有值,必須確定的情況下才能使用,使用一定要慎重。例如:

interface IPerson {
    name: string,
    age: number,
    friend?: {        // 可選屬性,不一定存在
        name: string
    }
}

const p1: IPerson = {   // 聲明p1
    name: 'www',
    age: 18,
    friend: {       // 擁有friend對象
        name: 'bbb'
    }
}

p1.friend.name = 'kobe'  // 默認(rèn)情況下報錯,因為是可選的所以ts不知道有沒有

// 兩種解決方式  類型縮小和非空類型斷言

// 類型縮小
if(p1.friend) {
    p1.friend.name = "kobe"
}

// 非空類型斷言   一定有這個參數(shù)
p1.friend!.name

字面量類型

可以限制某個變量必須傳哪個或者哪些字面量,通常是聯(lián)合類型一起使用,因為一個字面量類型沒意義

// 例 1
type Direction = 'left' | 'right' | 'up' | 'down'  // 定義方向類型
const d1: Direction = 'left'   

// 例 2
type MethodType = 'get' | 'post'  // 定義請求方式類型
function request(url: string, method: MethodType) {
    ...
}

函數(shù)類型

函數(shù)作為JS的一等公民,它可以作為參數(shù)也可以作為返回值傳遞。那么在TS中函數(shù)是否也可以有自己的類型?
答案是肯定的,我們可以編寫\color{red} {函數(shù)類型表達(dá)式}來表示函數(shù)類型

type CalcType = (num1: number, num2: number) => number  // 函數(shù)類型別名

function calc(calcFn: CalcType ) {
  const num1 = 20
  const num2 = 30
  return calcFn(num1, num2)
}

TypeScript對函數(shù)傳入的參數(shù)個數(shù)不進(jìn)行檢測

函數(shù)調(diào)用簽名

除了上面的函數(shù)類型表達(dá)式,如果我們站在對象的角度來看待這個函數(shù),它不僅僅可以調(diào)用而且也有其他的屬性,比如:

interface IBar {
  name: string
  age: number
  // 這就是函數(shù)調(diào)用簽名
  (n1: number): number
}

const bar: IBar = (n1: number): number => {
  return n1
}

bar.name = 'aaa'
bar.age = 19
bar(123)

構(gòu)造簽名

有時候我們定義一個函數(shù)時是希望把它當(dāng)作一個構(gòu)造函數(shù)來使用的,那么在TS里就要通過構(gòu)造簽名來明確這個函數(shù)是可以new的

class Person {}

interface ICTORPerson {
  new (): Person // 構(gòu)造簽名,返回一個Person類型對象
}

function factory(fn: ICTORPerson) {
  const f = new fn()
  return f
}

factory(Person)

函數(shù)的可選參數(shù)和默認(rèn)值

函數(shù)的可選值通過?來設(shè)置,跟對象屬性是一樣的寫法。
函數(shù)的默認(rèn)值跟ES6的寫法是一樣的,使用 =來設(shè)置

function foo(x: number, y?: number, z = 199) {
  if (y !== undefined) {
    console.log(y)
  }
}

foo(10, 20) // 不報錯
foo(19) // 同樣不會報錯
foo(111, 222, undefined) // 依舊不報錯

我們發(fā)現(xiàn),默認(rèn)值參數(shù)是可以接受undefined的,這就是語言設(shè)計的小細(xì)節(jié)。因為ts是幫我們進(jìn)行開發(fā)的而不是給我們造成煩惱的,所以這些小細(xì)節(jié)是很多的。

TypeScript面向?qū)ο?/h1>

成員修飾符

在TypeScript中類的屬性和方法支持這幾種修飾符:public private protected readonly

  • public 修飾的是在任何地方可見、公有的屬性或方法,默認(rèn)屬性就是public
  • private 修飾的是在同一類中可見、私有的屬性或方法
  • protected 修飾的是僅在類自身和子類中可見、受保護(hù)的屬性或方法
  • readonly 修飾的是只讀的屬性,不可以進(jìn)行修改
  class Love {
    name: string    // 公開的
    public age: number   // 公開的
    private _height: number  // 私有的,有個不成文的規(guī)定就是前面加個下劃線
    protected friend: { name: string }  // 受保護(hù)的
    readonly children: string  // 只讀的
  }

但是這種寫法是比較麻煩的,我們可以通過使用參數(shù)屬性的方法使代碼更加整潔

// 參數(shù)屬性
class Person {
  constructor(public name: string, private age: number, readonly height: number) {}

  running() {
    console.log(this.age, 'running')
  }
}

setter和getter

通常是對于類中的私有屬性,為其添加一個set和get方法,用來進(jìn)行攔截操作

class Abc {
    private _name: string
    private _age: number
    
    constructor(name: string, age: number) {
        this._name = name
        this._age = age
    }

    get name() {
        return this._name
    }

    set age(newVal: number) {   // 設(shè)置的時候進(jìn)行攔截操作
        if(newVal >= 0 && newVal < 150) {
            this._age = newVal
        }
    }
    
}

抽象類abstract

我們知道,面向?qū)ο笥腥蟾拍睿?strong>封裝繼承多態(tài),而繼承又是多態(tài)的前提。
我們可以通過多態(tài)來實(shí)現(xiàn)更加靈活的調(diào)用,并且由于父類本身可能并不需要具體實(shí)現(xiàn)某些方法,只需要定義,我們就可以將這個方法定義成抽象方法。
抽象類會有以下幾個特點(diǎn):

  • 抽象類不能被實(shí)例化(不能new)
  • 抽象類可以包含抽象方法,也可以包含具體實(shí)現(xiàn)的方法
  • 有抽象方法的類必須是一個抽象類
  • 抽象方法必須被子類實(shí)現(xiàn),否則該類必須是一個抽象類
abstract class Shape {
  abstract getArea()
}
// 矩形
class Rectangle extends Shape {
  constructor(public width: number, public height: number) {
    super()
  }

  getArea() {
    return this.width * this.height
  }
}
// 圓形
class Circle extends Shape {
  constructor(public radius: number) {
    super()
  }

  getArea() {
    return this.radius ** 2 * Math.PI
  }
}
// 三角形
class Triangle extends Shape {
  getArea() {
    return 100
  }
}
// 通用函數(shù)
function calcArea(shape: Shape) {
  return shape.getArea()
}

calcArea(new Rectangle(10, 20))
calcArea(new Circle(20))
calcArea(new Triangle())

索引簽名

用來聲明該類型可以通過索引來進(jìn)行訪問數(shù)據(jù)

interface ICollection {
  // 索引簽名,要求該類型必須可以通過索引訪問
  // index: 自定義標(biāo)識符,見名知意所以取index
  // number:表示你想通過什么類型索引訪問數(shù)據(jù)
  // string:訪問的索引值是什么類型的
  [index: number]: string
  length: number
}

function iteratorCollection(coll: ICollection) {
  console.log(coll[0])
  console.log(coll[1])
}

const names: string[] = ['foo', 'bar', 'baz']
const tuple: [string, string] = ['lamo', '18']

iteratorCollection(names) // 不報錯
iteratorCollection(tuple) // 不報錯
iteratorCollection({
  name: 'lll',
  age: 19,
  length: 10
}) // 報錯

TypeScript泛型編程

什么是泛型

泛型通俗點(diǎn)講實(shí)際上就是一種類型參數(shù)化的語法,它可以將類型像形參一樣由調(diào)用者傳入。

function bar(arg: number | string) {
    return arg
}

const res1 = bar("aaa")
const res2 = bar(111)

// 上面這種接受不同類型參數(shù)時,雖然使用聯(lián)合類型可以避免報錯,但是返回值也缺失了類型,對返回值進(jìn)行操作時也有可能報錯

// 下面這種做法就是把類型傳入函數(shù)中,就可以明確類型,方便進(jìn)行操作
function baz<Type>(arg: Type): Type {
    return arg
}

const res3 = baz<string>('aaa')
const res4 = baz<number>(11234)

泛型接口和泛型類

在定義接口或者類的時候我們可能希望也是動態(tài)的傳遞一些類型,這時候就可以使用到泛型接口泛型類

interface IKun<T = string> {  // 設(shè)置默認(rèn)類型
    name: T,
    age: number,
    slogin: T
}

// 傳入字符串類型
const iKun1: IKun<string> = {
    name: 'kunkun',
    age: 18,
    slogin: "你干嘛"
}
// 傳入number類型
const iKun2: IKun<number> = {
    name: 111,
    age: 55,
    slogin: 534
}
// 不傳類型使用默認(rèn)值
const ikun3:IKun = {
    name: 'kunns',
    age: 88,
    slogin: "你太美"
}
// 泛型類
class Point<Type = number> {
    x: Type
    y: Type
    constructor(x: Type, y: Type) {
        this.x = x
        this.y = y
    }
}

const p1 = new Point(10, 20)   // 類型推導(dǎo)number,直接傳進(jìn)去
const p2 = new Point<number>(20, 30)  // 手動確定類型
const p3 = new Porin("111","222")  // 類型推導(dǎo)string

泛型約束

有時候我們希望傳入的類型有某些共性,但是這些共性不在同一類型中:

  • 比如string和array都有l(wèi)ength,或者某些對象也有l(wèi)ength屬性
  • 那么只要是擁有l(wèi)ength屬性都可以作為我們的參數(shù)類型,那么怎么操作呢

想要保持泛型傳過來的原來的類型并且需要對傳入值的約束,這就需要用到extends關(guān)鍵字了

interface ILength {
    length: number
}

// 如果不繼承自ILength那就什么都可以傳了,不需要有l(wèi)ength屬性
// Type 主要用于記錄已經(jīng)成功傳入?yún)?shù)的類型
// ILength 用于約束傳入的參數(shù),它有沒有l(wèi)ength屬性
function getInfo<Type extends ILength>(args: Type): Type {
    return args
}

const info1 = getInfo("aaa")
const info2 = getInfo(['aaa', 'bbb', 'cc'])
const info3 = getInfo({ length: 100 })

泛型參數(shù)約束

在JS中我們通過key獲取對象屬性值時,即使是一個不存在的key那么在編寫代碼期間也不會報錯,但是在ts中可以通過泛型參數(shù)約束來實(shí)現(xiàn)的

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

const info = {
  name: 'foo',
  age: 18,
  height: 1.88
}

const name = getObjectProperty(info, 'name') // 不報錯
const address = getObjectProperty(info, 'address') // 報錯

export {}

上述代碼會報錯類型“"address"”的參數(shù)不能賦給類型“"name" | "age" | "height"”的參數(shù)。這也就說明,keyof會將對象所有的key作為一個字面量聯(lián)合類型返回

映射類型

有的時候一個類型需要基于另一個類型,但是你又不想拷貝一份,這時候就可以考慮使用映射類型

  • 大部分內(nèi)置的工具都是通過映射類型來實(shí)現(xiàn)的
  • 大多數(shù)類型體操題目也是通過映射類型來完成的
    映射類型建立在索引簽名的語法上:
  • 映射類型就是使用了屬性keys聯(lián)合類型的泛型
  • 屬性keys多是通過keyof創(chuàng)建,然后循環(huán)遍歷鍵名創(chuàng)建的一個類型
interface IPerson {
  name: string
  age: number
}

type MapType<T> = {
  [property in keyof T]: T[property]
}

type NewPerson = MapType<IPerson>

那么我們?yōu)槭裁匆截惸兀?/strong>
答案其實(shí)是因為映射類型可以對拷貝的類型進(jìn)行修飾,可以在不改變原類型的情況下添加一些修飾符:

interface IPerson {
  name?: string
  age: number
}

type MapType<T> = {
  +readonly [property in keyof T]-?: T[property]
}

type NewPerson = MapType<IPerson>

// type NewPerson = {
//   readonly name: string;
//   readonly age: number;
// }

我們能看到原類型IPerson的name屬性是可選類型,但是映射類型中可以通過-?來刪除這個可選特性,并且給所有屬性加上readonly特性

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容