TypeScript,从0到入门带你进入类型的世界
從0到入門進入TS的世界
- 一、什么是TypeScript?
- 1、編程語言的類型
- 2、TypeScript究竟是什么?
- 二、為什么要學習TypeScript?
- 1、程序更容易理解
- 2、效率更高
- 3、更少的錯誤
- 4、非常好的包容性
- 5、一點小缺點
- 三、typescript入門
- 1、如何安裝TypeScript
- 2、查看版本號
- 四、Typescript數據類型
- 1、原始數據類型和Any類型
- (1)原始數據類型
- (2)Any 類型
- 2、數組和元組
- (1)數組
- (2)元組
- 3、interface 接口
- 4、function函數
- 5、類型推論、聯合類型和類型斷言
- (1)類型推論
- (2)聯合類型
- (3)類型斷言
- 五、Typescript中的類:class
- 1、類的定義
- (1)類(Class)
- (2)對象(Object)
- (3)面向對象(OOP)的三大特性
- 2、Typescript中的類
- 3、類和接口
- (1)解決什么問題
- (2)如何解決
- (3)舉個例子
- 六、枚舉
- 1、普通枚舉
- 2、常量枚舉
- 七、泛型
- 1、普通泛型
- 2、約束泛型
- 3、泛型在類和接口中的使用
- (1)泛型在類中的使用
- (2)泛型在接口中的使用
- 八、類型別名
- 1、類型別名
- 2、字符串字面量
- 3、交叉類型
- 九、聲明文件
- 1、 .d.ts 文件引入
- 2、npm安裝
- 十、內置類型
- 十一、結束語
眾所周知, js 是一門弱類型語言,并且規范較少。這就很容易導致在項目上線之前我們很難發現到它的錯誤,等到項目一上線,渾然不覺地, bug 就UpUp了。于是,在過去的這兩年, ts 悄悄的崛起了。
周一隨著一波熱潮,也開始進入了 ts 的世界,不得不感嘆 ts 的靜態美。
下面的文章中將講解我對 TS 入門的一些歸納總結。一起來了解一下吧!
一、什么是TypeScript?
1、編程語言的類型
| JavaScript | C,C++,C#,JAVA |
2、TypeScript究竟是什么?
-
Typescript,即 Javascript that scales ;
-
ts 把不看重類型的動態語言 JS 轉變成關注類型的靜態語言;
-
可以說ts是靜態類型風格的類型系統;
-
從 es6 到 es10 甚至是 esnext 的語法支持;
-
兼容各種瀏覽器,各種系統,各種服務器,完全開源。
二、為什么要學習TypeScript?
1、程序更容易理解
動態語言存在函數或者方法中其輸入輸出的參數類型等問題,同時,動態語言還受到各種各樣的約束,比如需要手動調試等等。那么有了 ts ,代碼本身就可以解決上述問題了, ts 讓程序更容易理解,程序理解我們,我們就可以少干很很多事情。
就像我們在與別人交談時,假如我們邏輯很清晰的表達給對方,對方馬上聽懂了,并且理解了我們,我們也很省力,不用長篇大論的介紹。
2、效率更高
ts 可以在在不同的代碼塊和定義中進行跳轉,并且代碼有補全功能。
同時, ts 還有豐富的接口提示,可以通過使用 . 來提示所有的接口內容。
3、更少的錯誤
ts 在編程期間,就可以發現大部分的錯誤。這樣就可以杜絕掉一些比較常見的錯誤,也使得后面程序運行更加通暢。
4、非常好的包容性
ts 可以完全地兼容 Javascript ,同時,如果要引入像 JQuery 之類的第三方庫時,可以單獨編寫類型文件來引入這些庫。
5、一點小缺點
相比于 js 來講, ts 在學習之初,需要去習慣一些規范,短期內會增加一點學習成本。但短期的學習成本增加將會使得在后期的開發當中減少很多不必要的錯誤和麻煩,間接的也為自己的開發帶來很大的益處。
閑談到此結束,讓我們一起來進入 ts 的世界吧!
三、typescript入門
1、如何安裝TypeScript
npm install -g typescript2、查看版本號
tsc -v四、Typescript數據類型
1、原始數據類型和Any類型
(1)原始數據類型
//定義一個布爾值數據 let isDone: boolean = false//定義一個數字類型 let age: number = 20//定義字符串類型 let firstName: string = 'monday' let message: string = `Hello, ${firstName}`//定義undefind和null類型 let u: undefined = undefined let n: null = null//給數字賦值undefid let num: number = undefined(2)Any 類型
如果我們有時候不能確定一個數據是什么類型的話,那么我們可以用any類型來定義。比如:
//定義一個any類型數據 let notSure: any = 4 notSure = 'maybe a string' notSure = truenotSure.myName notSure.getName()2、數組和元組
(1)數組
//聲明一個數字類型的數組 //注意:后面的數組只能傳遞數字,傳遞其他類型的數據都會報錯 let arrOfNumbers: number[] = [1, 2, 3]; arrOfNumbers.push(3)function test(){//arguments 為類數組console.log(arguments) }(2)元組
//確定一個元組里面的內容和數量,下面表示確定user這個元組必須且只能接收兩個參數 //同時第一個屬性必須是String類型,第二個屬性是Number類型 let user: [String, Number] = ['abc', 13]3、interface 接口
interface的定義:
- 對 對象Object 的形狀 (shape) 進行描述;
- Duck Typing(鴨子類型)。
我們來看一段代碼:
interface Person{// readonly表示只讀狀態readonly id: number,name: String,//加一個問號表示該參數可選可不選age?: number }let monday: Person = {id: 1,name: 'monday',age: 18 }monday.id = 12323; //因為加了readonly,所以此時訪問不了,會報錯4、function函數
function函數是什么:
- 在 JS 中,函數是一等公民。
- 函數和其他類型的對象都一樣,可以作為參數,可以存入數組,也可以被另外一個函數返回,可以被賦值給另外一個變量。
- 函數主要由兩個部分組成:輸入(傳參)和輸出(返回結果)。
我們來看個例子:
function add(x: number, y: number, z?: number): number{if(typeof z === 'number'){return x + y + z;}else{return x + y;} }let result = add(1, 2, 3); console.log(result); //6通過以上函數,我們實現了兩個樹或者三個樹的相加操作。此時,需要我們注意的是,可選參數后面不能再添加不確定參數,否則程序就會發生混亂。比如:
function add(x: number, y: number, z?: number, t: number): number{if(typeof z === 'number'){return x + y + z;}else{return x + y;} }以上代碼中的 t 是肯定不被允許添加的,因為前面已經有了可選參數 z ,而后面又突然健冒出來個 t ,想想都不太合理。
到這里,假設我們由一個新的變量名,名字叫 add2 。這個時候我們想要給它像 add 函數一樣的類型。那么該怎么處理呢?
let add2: (x:number, y:number, z?: number) => number = add注意上面這個箭頭 => 不是 ES6 中的箭頭函數,而是 ts 中聲明函數類型返回值的方法。
上面這個語句中就說明了, add2 返回的值是一個 number 類型數值,并且讓它等于 add 函數。同時,要記得的是,在 ts 當中,凡是在 : 后面都是聲明在聲明類型。
上面這樣寫好像有點冗余,我們來用 interface 來實現同樣的效果。
在第3點的 interface 中我們了解到, interface 是對對象的形狀進行描述,但值得注意的是, interface 也可以是對函數的形狀進行描述。我們用代碼來實現一下。
interface ISum {(x: number, y: number, z?: number) : number }let add2: ISum = add通過以上代碼,我們看到,用 interface 來封裝一個函數的返回值來行,看起來優雅了不少。這里先體會一下, interface 的強大之處,在后面還會繼續講解。
5、類型推論、聯合類型和類型斷言
(1)類型推論
有時候我們還沒有給一個數據定義類型,就直接給它賦值了。這個時候我們要怎么來判斷呢。這個數據的類型呢?
比如:
let str = 123當出現這樣的情況時,編譯器會直接給 str 賦上 number 類型。那么此時如果我們想這么干:
let str = 123 str = 'asd' //會報錯結果當然時不行的。當第一次賦值的時候,編譯器就已經給 str 一個 number 類型,認定 str 就是 number 類型。而后我們還想要給 str 賦值上一個 string 類型的數據,肯定是會報錯的。
(2)聯合類型
有時候我們對一個數據的類型不夠確定,比如說不知道某一個數據它是 number 類型還是 string 類型。這個時候我們就可以用聯合類型來進行一波操作。
let numberOrString: number | string通過這種方式,我們對我們所定義的屬性 numberOrString 進行聯合類型操作。
一般情況下,聯合類型會結合類型斷言來進行使用。接下來我們來講類型斷言。
(3)類型斷言
1) 當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型中共有的屬性或方法,而有時候呢,我們確實需要在還不確定類型的時候就訪問其中一個類型特有的屬性或方法。因此我們采用類型斷言的方式將其指定為一個類型。(這么做只是先欺騙了 ts ,讓其信任我們所指定的類型)
let str = 123 function getLength(input: string | number) : number{// 用as對input進行類型斷言,先給input指定一個類型,后面判斷不是再進行轉換//注意:類型斷言只做類型選擇,而不做類型轉換const str = input as stringconsole.log(typeof str)if(str.length){return str.length}else{const number = input as numberreturn number.toString().length} }2) 看到這里,已經開始感覺到類型斷言的神奇之處。但用上面這種方法感覺好像還有一點點冗余,于是我們引入一個 type guard ,即類型守護。我們來看下實現方式。
let str = 123 function getLength2(input: string | number): number{if(typeof input === 'string'){return input.length}else{return input.toString().length} }五、Typescript中的類:class
在 js 中我們用了構造函數和原型鏈的方式來實現繼承,同時在 ES6 中出現了 class 類繼承的方法。那在 typescript 中呢,繼承的方法又更加豐富了。讓我們一起來一探究竟吧!
1、類的定義
我們先來看下類的定義。
(1)類(Class)
類定義了一切事物的抽象特點,包含它的屬性和方法。比如:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} }閱讀以上代碼我們可以知道,通過 class 可以定義一個類。
(2)對象(Object)
對象 Object ,就是類的實例。舉個例子: 🙆?♂?
我們可以把類 class 比喻成一張藍圖,比如說汽車是一個 class ,那么它就像是一張造汽車的圖紙。第二個是 Object, Object 通過 new 生成,那么前面有了汽車的藍圖,我們現在就可以創造實實在在的汽車了。我們可以說一輛特斯拉是汽車的實例,也可以說寶馬是汽車的另外一個實例。
同樣我們用上面的例子來做衍生。具體如下:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} } const snake = new Animal('lily') console.log(snake.run())閱讀以上代碼我們可以知道,我們定義了一個 snake ,這個 snake 繼承了 Animal 類,因此它就可以用 Animal 類的屬性和方法。
此時打印結果如下:
(3)面向對象(OOP)的三大特性
面向對象的三大特性分別為:封裝、繼承、多態 。
- 封裝: 指將數據的操作細節隱藏起來,只暴露對外的接口。那這樣子的話,對于外界的調用端來說,他們不需要也不可能知道細節,只能通過對外的接口來訪問該對象。
- 繼承: 子類可以繼承父類,子類除了擁有父類的所有特征外,還會擁有一些更具體的特性。
- 多態: 由繼承產生的相關不同的類,對同一個方法可以有不同的響應。比如,貓和狗,他們都可以繼承 Animal 類,但是他們分別實現 run() 方法,此時呢,針對某一個實例,我們無需了解它是貓還是狗,這個時候可以直接調用 run() ,程序會自動判斷出來,應該如何去執行這個方法。
同樣,我們用上面的代碼做衍生,來看繼承和多態是怎么樣的。
繼承:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} } const snake = new Animal('lily') // console.log(snake.run())class Dog extends Animal{bark(){return `${this.name} is barking`} }const xiaoqi = new Dog('xiaoqi') console.log(xiaoqi.run()) console.log(xiaoqi.bark())此時打印結果如下:
從上面可以看到, Dog 繼承了 Animal 類,此時 Dog 就擁有了 Animal 類的屬性和方法。而 xiaoqi 實例化了 Dog ,因此它也擁有 Dog 的屬性和方法。
多態:
class Animal{// 構造函數是實例化執行時候的邏輯constructor(name){this.name = name}run(){return `${this.name} is running`} } const snake = new Animal('lily') // console.log(snake.run()) //----------------------------------- class Dog extends Animal{bark(){return `${this.name} is barking`} }const xiaoqi = new Dog('xiaoqi') console.log(xiaoqi.run()) console.log(xiaoqi.bark()) //----------------------------------- class Cat extends Animal{// 靜態方法不需要進行實例化,直接在類上調用即可static categories = ['mammal']constructor(name){super(name)console.log(this.name)}run(){return `Meow, ` + super.run() } } const maomao = new Cat('maomao') console.log(maomao.run()) // 直接訪問靜態屬性 // 為什么要有靜態屬性?當定義和實例沒有太大關系時,可以考慮使用靜態方法實現 console.log(Cat.categories)此時打印結果如下:
閱讀代碼我們可以發現, xiaoqi 繼承了 dog 的 run() 方法,而 Cat 繼承了 Animal 類,但是它對 run() 方法進行了改寫,因此最終的 run() 方法為改寫后的效果。
所以, maomao 繼承了 Cat 類,最后 maomao 調用 run() 方法時,就會調用 Cat 里面改寫的 run() 方法,而不是 Animal 類的 run() 方法。
這樣, xiaoqi 和 maomao 雖然同樣繼承自 Animal 類,但他們調用 run() 方法的結果各自相互獨立,如此,就實現了多態。
同時,我們還要注意一個點,就是靜態屬性。大家可以看到上面定義的 categories ,用了 static 來定義它為靜態屬性。當把變量定義為靜態屬性時,則當外部需要該靜態方法時,不需要進行實例化,之類在類上調用即可。
那么問題來了,我們什么時候才需要有靜態屬性呢?
其實,當定義的內容和實例沒有太大關系時,就可以考慮使用靜態方法。比如常量的使用,常量基本是固定的,不會變的,所以我們可以考慮直接使用靜態方法來獲取它。
2、Typescript中的類
Typescript是通過什么方式來增強類的呢,typescript一般通過以下四種修飾符來增強類:
| public | 修飾的屬性或方法是公有的 |
| private | 修飾的屬性或方法是私有的 |
| protected | 修飾的屬性或方法是受保護的 |
| readonly | 只能讀不能寫 |
有了以上這四種修飾符呢,我們就可以給類的方法和屬性進行權限管理。為什么要做權限管理呢?因為總有些內容,我們是不愿意暴露給外部使用的,所以需要進行權限管理。
值得注意的是,對于 protected 這個修飾符來說,只有子類可以訪問父類的屬性和方法,其他實例都不能訪問。這其實可以把 protected 這個變量理解為遺產,父類的東西直接給子女繼承,其余外部人員一概不能訪問。
3、類和接口
(1)解決什么問題
繼承存在著這樣一個困境,在面向對象的世界中,一個類只能繼承另外一個類,有時候同類之間有一些共同的特性,但是使用子類來繼承父類又很難完成。于是接口就出現了。
(2)如何解決
類可以使用 implements 來實現接口,怎么做呢?我們可以把這些相同的特性提取成接口,然后用 implements 這個關鍵字來實現,這樣就大大提高了面向對象的靈活性。
(3)舉個例子
假如我們現在要讓一輛汽車和一部手機來實現打開播放器的功能,那么我們會這么實現:
class Car{switchRadio(trigger: boolean){} }class CellPhone{switchRadio(trigger: boolean){} }但是這樣子看起來好像就沒有特別雅觀。于是我們可以寫一個打開播放器的接口,然后用 implements 來實現這個功能。代碼如下:
interface Radio{switchRadio(trigger: boolean): void }class Car implements Radio{switchRadio(trigger: boolean){} }class CellPhone implements Radio{switchRadio(trigger: boolean){} }這樣,就讓Car和CellPhone實現了打開播放器的功能。
接下來,我們繼續寫一個接口,可以實現檢查電池電量的功能。并且讓手機不僅可以打開播放器,還可以檢查電池電量。代碼如下:
interface Radio{switchRadio(trigger: boolean): void }interface Battery{checkBatteryStatus(): void }class Car implements Radio{switchRadio(trigger: boolean){} }class CellPhone implements Radio,Battery{switchRadio(trigger: boolean){}checkBatteryStatus(){} }閱讀代碼我們可以發現,我們要給繼承兩個接口 Radio,Battery ,這樣看似乎還有點冗余。于是我們可以這樣實現:
interface Radio{switchRadio(trigger: boolean): void }interface RadioWithBattery extends Radio{checkBatteryStatus(): void }class Car implements Radio{switchRadio(trigger: boolean){} }class CellPhone implements RadioWithBattery{switchRadio(trigger: boolean){}checkBatteryStatus(){} }通過 interface 繼承 interface ,最終用 implement 去抽象和驗證類的屬性和方法,達到抽離功能的目的。
相信通過以上的簡單了解,大家能感受到一點 interface 的奇妙之處。
六、枚舉
1、普通枚舉
枚舉常使用于我們在程序中需要做權限管理或者做判斷時等各種場景。枚舉比較簡單,下面直接用代碼演示:
enum Direction{Up,Down,Left,Right }console.log(Direction.Up) //0 console.log(Direction.Down) //1 console.log(Direction.Left) //2 console.log(Direction.Right) //3 console.log(Direction[0]) //Up除了以上基本用法外,我們還可以給枚舉賦值:
enum Direction{Up = 10,Down,Left,Right }console.log(Direction.Up) //102、常量枚舉
我們來定義一個常量,與 enum 做判斷。
enum Direction{Up = 'Up',Down = 'Down',Left = 'Left',Right = 'Right' }//定義一個常量,直接與enum做判斷 const value = 'Up'; if(value === Direction.Up){console.log('go Up!') // go Up! }使用常量枚舉可以有效地提升性能,常量會內聯枚舉的任何用法,而不會把枚舉變成任意的 Javascript 代碼。
這樣一說,那是不是所有的 enum 都可以使用常量枚舉呢?答案自然是否定的。
枚舉的值有兩種類型,一種是常量值枚舉(constant),一種是計算值枚舉(computed)。只有常量值枚舉可以進行常量枚舉,而計算值枚舉不能進行常量枚舉。
七、泛型
接下來我們來講 TypeScript 中最難的一部分,泛型。
1、普通泛型
泛型,即 generics 。指在定義函數、接口或類的時候,我們不預先指定類型,而是在使用的時候再指定類型和其特征。
可以理解為,泛型就相當于一個占位符或者是一個變量,在使用時再動態的填入進去,填進去以后既可以來確定我們的類型值。
接下來我們用代碼來演示一下:
function echo<T>(arg: T): T{return arg }const result = echo(true) console.log(result) // true我們通過 <> 來定義一個未知的泛型,之后當我們給它賦值時,就可以對應值的數據類型。
現在我們再用泛型來定義一個 number 類型的數組。具體代碼如下:
// 早期定義一個number類型的數組 let arr: number[] = [1, 2, 3] // 用泛型定義一個number類型的數組 let arrTwo: Array<number> = [1, 2, 3]假如我們現在要調換兩個元素的位置,那么用泛型我們可以這么實現。具體代碼如下:
function swap<T, U>(tuple: [T, U]): [U, T]{return [tuple[1], tuple[0]] }const result2 = swap(['abc', 123]) console.log(result2[0]) //123 console.log(result2[1]) //abc通過泛型,我們就順利調換了兩個元素的位置。
2、約束泛型
在泛型中使用 extends 關鍵字,就可以讓傳入值滿足我們特定的約束條件,而不是毫無理由的隨意傳入。
比如,我們想要讓我們定義的內容是一個數組,我們可以這么處理。具體代碼如下:
function echoWithArr<T>(arg: T[]): T[]{console.log(arg.length)return arg } const arrs = echoWithArr([1, 2, 3])這樣,就把 arrs 定義為一個數組。
假設我們現在想要讓我們定義的內容可以訪問到 length 方法,那么我們需要加一點佐料。具體代碼如下:
interface IWithLength{length: number }function echoWithLength<T extends IWithLength>(arg: T): T{console.log(arg.length)return arg }const str = echoWithLength('str') const obj = echoWithLength({ length: 10, width: 20 }) const arr2 = echoWithLength([1, 2, 3])通過 extends 關鍵字來繼承特定的 interface ,使得我們定義的內容 str , obj ,arr2 達到可以訪問length方法的目的。
通過以上舉例,我們可以知道,泛型可以用來靈活的約束參數的類型,參數不需要是個特別死板的類型,而可以通過我們的約束來達到我們想要的目的。
3、泛型在類和接口中的使用
(1)泛型在類中的使用
class Queue<T>{private data = []push(item: T){return this.data.push(item)}pop(): T{return this.data.shift()} } // 確定這是一個number類型的隊列 const queue = new Queue<number>() queue.push(1) console.log(queue.pop().toFixed())(2)泛型在接口中的使用
interface KeyPair<T, U>{key: Tvalue: U } let kp1: KeyPair<number, string> = {key: 1, value: 'str'} let kp2: KeyPair<string, number> = {key: 'str', value: 2}通過以上代碼演示可以發現,泛型就像是創建了一個擁有特定類型的容器,就仿佛給一個容器貼上標簽一樣。
八、類型別名
1、類型別名
類型別名,即 type aliase 。類型別名可以看作是一個快捷方式,可以把一個寫起來很繁瑣的類型創建一個簡單的寫法。比如:
//用以下這種寫法,每次都要寫一長串的(x: number, y: number) => number let sum: (x: number, y: number) => number const result = sum(1, 2)//用type給類型進行別名 type PlusType = (x: number, y: number) => number let sum2: PlusType const result2 = sum2(2, 3)//一個類型可以是字符串也可以是數字 type StrOrNumber = string | number let result3: StrOrNumber = '123' result3 = 1232、字符串字面量
字符串字面量,指可以提供一系列非常方便使用的常量寫法。比如:
const str: 'name' = 'name' const number: 1 = 1 type Direction = 'Up' | 'Down' | 'Left' | 'Right' let toWhere: Direction = 'Left'3、交叉類型
交叉類型,使用 type 這個擴展對象的一種方式。比如:
interface IName{name: string } type IPerson = IName & {age: number} let person: IPerson = {name: 'monday', age: 18}九、聲明文件
我們在寫ts時,難免會有遇到要引入第三方庫的時候。這個時候就需要ts來做特殊處理。主要有以下兩種做法:
1、 .d.ts 文件引入
假設我們要引入JQuery庫來使用,那么我們可以在外部新增一個 JQuery.d.ts 文件,文件內代碼如下:
declare var JQuery: (selector: string) => any;之后便可以在我們定義的 ts 文件下引用 JQuery 相關庫的內容。比如:
jQuery('#foo')2、npm安裝
我們也可以安裝對應的第三方庫的 npm 包。假如我們現在要引入一個 JQuery 庫,那么我們可以這么處理。
npm install --save @type/jquery十、內置類型
我們在寫 ts 代碼時,其實不知不覺已經使用了很多的內置對象。對象呢,是指根據標準(標準指 ECMA 、 DOM 等標準),在全局作用域 global 上面存在的對象。那我們在運行 tsc 時,這些內置的對象就會被當作附加的禮物給程序加載進行。接下來我們來體會一下幾種常見的內置對象。
全局對象:
// global object 全局對象 const a: Array<number> = [1, 2, 3] const date = new Date() date.getTime() const reg = /abc/ reg.test('abc')內置對象:
// build-in object 內置對象 Math.pow(2, 2)DOM和BOM對象:
// DOM 和 BOM let body = document.body let allLis = document.querySelectorAll('li') allLis.keys()document.addEventListener('click', e => {e.preventDefault() })功能性類型:
// Utility Types 功能性類型 interface IPerson{name: stringage: number }let monday: IPerson = {name: 'monday', age: 20}//可選屬性 type IPartial = Partial<IPerson> let monday2: Ipartial = {name: 'monday'}//移除某一個屬性 type Omit = Omit<IPerson, 'name'> let monday3: Omit = {age: 20}十一、結束語
關于 ts 的入門講到這里就結束啦!希望大家能對 ts 有一個簡單的認識!
如本文有不理解或有誤的地方歡迎評論區評論或私信我交流!我們下期見!
- 關注公眾號 星期一研究室 ,不定期分享學習干貨,學習路上不迷路~
- 如果這篇文章對你有用,記得點個贊加個關注再走哦~
總結
以上是生活随笔為你收集整理的TypeScript,从0到入门带你进入类型的世界的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试中的网红虚拟DOM,你知多少呢?深入
- 下一篇: 红魔:首款国产 49 英寸 OLED 显