前言
我始終相信,任何新技術(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í)行了。

當(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)行類型檢測,聲明的類型可以被稱為。
所以一個完整的聲明格式是: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ù)是否也可以有自己的類型?
答案是肯定的,我們可以編寫來表示函數(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特性
