溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

有哪些關(guān)于TypeScript的知識點(diǎn)

發(fā)布時(shí)間:2021-10-28 15:42:18 來源:億速云 閱讀:192 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“有哪些關(guān)于TypeScript的知識點(diǎn)”,在日常操作中,相信很多人在有哪些關(guān)于TypeScript的知識點(diǎn)問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”有哪些關(guān)于TypeScript的知識點(diǎn)”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

開始 TypeScript

學(xué)習(xí)準(zhǔn)備

開始 TypeScript(以下簡稱 TS)正式學(xué)習(xí)之前,推薦做好以下準(zhǔn)備:

  • Node 版本 > 8.0

  • IDE(推薦 VS Code,TS 是微軟推出的,VS Code 也是微軟推出,且輕量。對 TS 代碼更友好)

有哪些關(guān)于TypeScript的知識點(diǎn)

初識 TypeScript

打開 TypeScript 官網(wǎng)可以看到官方對 TS 的定義是這樣的

JavaScript and More A Result You Can TrustGradual Adoption

這三個(gè)點(diǎn)就很好地詮釋了 TypeScript 的特性。在此之前,先來簡單體驗(yàn)下 TypeScript 給我們的編程帶來的改變。

這是一個(gè) .js 文件代碼:

let a = 123a = '123'

這是 .ts 文件代碼:

let b = 123b = '123'

當(dāng)我們在 TS 文件中試圖重新給 b 賦值的時(shí)候,發(fā)生了錯(cuò)誤,鼠標(biāo)移動到標(biāo)紅處,系統(tǒng)提示:

Type ·"123"' is not assignable to type 'number'

原因是什么呢?

答案很簡單,在 TS 中所有變量都是靜態(tài)類型,let b = 123 其實(shí)就是 'let b:number = 123'。b 只能是 number  類型的值,不能賦值給其他類型。

TypeScript 的優(yōu)勢

  • TS 靜態(tài)類型,可以讓我們在開發(fā)過程中發(fā)現(xiàn)問題

  • 更友好的編輯器自動提示

  • 代碼語義清晰易懂,協(xié)作更方便

配上代碼來好好感受下這三個(gè)優(yōu)勢帶給我們的編程體驗(yàn)有多直觀,建議邊在編輯器上敲代碼。

先上最熟悉的 JS:

function add(data) {     return data.x + data.y }add()  //當(dāng)直接這樣寫,在運(yùn)行的時(shí)候才會有錯(cuò)誤告知 add({x:2,y:3})

再上一段 TS 代碼(如果對語法有疑問可以先不糾結(jié),后續(xù)會有講解,此處可以先帶著疑問)

interface Point { x: number, y: number } function tsAdd(data: Point): number {     return data.x + data.y }tsAdd()  //直接這樣寫,編輯器有錯(cuò)誤提示 tsAdd({ x: 1,y: 123})

當(dāng)我們在 TS 中調(diào)用 data 變量中的屬性的時(shí)候,編輯器會有想 x、y 屬性提示,并且我們直接看函數(shù)外部,不用深入,就能知道 data  的屬性值。這就是 TS 帶給我們相比于 JS 的便捷和高效。

TypeScript 環(huán)境搭建

搭建 TypeScript 環(huán)境,可以直接在終端執(zhí)行命令:

npm install -g typescript

然后我們就可以直接 cd 到 ts 文件夾下,在終端運(yùn)行:

tsc demo.ts

tsc 簡而言之就是 typescript complaire,對 demo.ts 進(jìn)行編譯,然后我們就可以看到該目錄下多了一個(gè)同名的 JS  文件,可以直接用 Node 進(jìn)行編譯。

到這里我們就可以運(yùn)行 TS 文件了,但是這只是一個(gè)文件,而且還要先手動編譯成 TS 在手動運(yùn)行 Node,有沒有一步到位的命令呢?當(dāng)然有,終端安裝  ts-node:

npm install -g ts-node

這樣我們可以直接運(yùn)行:

ts-node demo.ts

來運(yùn)行 TS 文件,如果要初始化 ts 文件夾,進(jìn)行 TS 相關(guān)配置,可以運(yùn)行:

tsc --init

關(guān)于相關(guān)配置,這里我們先簡單提下,后面將會分析常用配置,可以先自行打開 tsconfig.json  文件,簡單看下其中的配置,然后帶著疑問繼續(xù)往下看。

再理解下 TypeScript 中的 Type

正式介紹 TS 的語法之前,還需要再把開篇提到的靜態(tài)類型再來說清楚一些。

const a: number = 123

之前說過,代碼的意思是 a 是一個(gè) number 類型的常量,且類型不能被改變。這里我要說的深層意思是,a 具有 number  的屬性和方法,當(dāng)我們在編輯器調(diào)用 a 的屬性和方法的時(shí)候,編輯器會給我們 number 的屬性和方法供我們選擇。

有哪些關(guān)于TypeScript的知識點(diǎn)

TS 不僅允許我們給變量定義基礎(chǔ)類型,還可以定義自定義類型:

interface Point {     x: number     y: number } const a: Point = {     x: 2,     y: 3 }

把 a 定義為 Point 類型,a 就擁有了 Point 的屬性和方法。而我們把 a 定義為 Point 類型之后,a 必須 Point 上 的 x 和  y 屬性。這樣我們就把 Type 理解的差不多了。

TypeScript 的類型分類

類比于 JavaScript 的類型,TypeScript 也分為基礎(chǔ)類型和引用類型。

原始類型

原始類型分為 boolean、number、string、void、undefined、null、symbol、bigint、any、never

JS 中也有的這里就不多解釋,主要說下之前沒有見過的幾種類型,但是需要注意一下的是我們在聲明 TS  變量類型的時(shí)候都是小寫,不能寫成大寫,大寫是表示的構(gòu)造函數(shù)。

void 表示沒用任何類型,通常我們會將其賦值給一個(gè)沒有返回值的函數(shù):

function voidDemo(): void {     console.log('hello world') }

bigint 可以用來操作和存儲大整數(shù),即使這數(shù)已經(jīng)超出了 JavaScript 構(gòu)造函數(shù) Number  能夠表示的安全整數(shù)范圍,實(shí)際場景中使用較少,有興趣的同學(xué)可以自行研究下。

any 指的是任意類型,在實(shí)際開發(fā)中應(yīng)該盡量不要將對象定義為 any 類型:

let a: any = 4 a = '4'

never 表示永不存在的值的類型,最常見的就是函數(shù)中不會執(zhí)行到底的情況:

function error(message: string): never {     throw new Error(message)     console.log('永不執(zhí)行') }function errorEmitter(): never {     while(true){} }

引用類型

對象類型:賦值時(shí),內(nèi)必須有定義的對象屬性和方法

const person: {     name: string     age: number } = {     name: 'aaa'     age: 18 }

數(shù)組類型:數(shù)組中每一項(xiàng)都是定義的類型。

const numbers: number[] = [1, 2, 3]

類類型:可以先不關(guān)注寫法,后面還會詳細(xì)講解。

class Peron {} const person: Person = new Person()

類型的介紹差不多就這么些知識點(diǎn),先在腦海里有個(gè)印象,不懂的地方可以繼續(xù)帶著疑問往下看。

TypeScript 類型注解和推斷

之前已經(jīng)講過 TypeScript 的類型和它的類型種類,這一小節(jié)還是想繼續(xù)把有關(guān)類型的知識講全,那么就是類型注解和類型推斷。

類型注解

let a: number a = 123

上面代碼中這種寫法就是類型注解,通過顯式聲明,來告訴 TS 變量的類型:

let b = 123

這里我們并沒有顯式聲明 b 的類型,但是我們在編輯器中把光標(biāo)放在 b 上,編輯器會告訴我們它的類型。這就是類型推斷。

簡單的情況,TS 是可以自動分析出類型,但是復(fù)雜的情況,TS 無法分析變量類型,我們就需要使用類型注釋。

// 場景一 function add(first,second) {     return first + second }const sum = add(1,2) // 場景二function add2(first: nnumber,second: number) {     return first + second }const sum2 = add2(1,2)

在場景一中,形參 first、second 的類型 TS 推斷為 any,且函數(shù)的返回值也是推斷為 any,因?yàn)檫@種情況下,TS  無法判斷類型,傳參的時(shí)候可能傳 number 或者 string 等。

場景二中,即使我們沒有定義 sum2 的類型,TS 一樣可以推斷出 number,這是因?yàn)?sum2 是由 first second  求和的結(jié)果,所以它一定是 number。

不管是類型推斷還是類型注解,我們的目的都是希望變量的類型是固定的,這樣不會把 typescript 變成 anyscript。

補(bǔ)充:函數(shù)結(jié)構(gòu)中的類型注解。

// 情況一  function add({ first }: {first: number }): number {     return first } // 情況二 function add2({first, second}: {first: number, second: number}): number {     return first + second } const sum2 = add({ first: 1, second: 2}) const sum2 = add2({ first: 1, second: 2})

TypeScript 進(jìn)階

配置文件

之前我們提到過,當(dāng)我們要運(yùn)行 TS 文件時(shí),執(zhí)行命令 tsc 文件名 .ts 就可以編譯 TS 文件生成一個(gè)同名 JS  文件,這個(gè)過程是怎么來的呢,或者如果我們想修改生成的文件名和文件目錄該怎么辦呢?

相信你已經(jīng)心里有答案了,沒錯(cuò),和 webpack 打包或者 babel 編譯一樣,TS 也有一個(gè)編譯配置文件 tsconfig.json。當(dāng)我們執(zhí)行ts  --init,文件目錄下就多了一個(gè) TS 配置文件,TS 編譯成 js,就是由 tsconfig 中配置而來。

為了驗(yàn)證下 tsconfig 文件確實(shí)會對 TS 文件編譯做配置,修改里面的:

"removeComments": true //移除文件中的注釋

然后新建一個(gè) demo.ts 文件:

// 這是一個(gè)注釋 const a: number = 123

執(zhí)行 tsc demo.ts,打開 demo.js 文件,發(fā)現(xiàn)注釋并沒有被移除,這是怎么回事,配置文件不生效?

真相是這樣的,當(dāng)我們直接執(zhí)行文件的時(shí)候,并不會使用 tsconfig 中的配置,只有我們直接執(zhí)行 tsc,就會使用 tsconfig 中的配置,直接運(yùn)行  tsc,你就發(fā)現(xiàn)了,amazing!

當(dāng)運(yùn)行 tsc 命令的時(shí)候,直接會先去找到 tsconfig 配置文件,如果沒有做其他改動,會默認(rèn)編譯根目錄下的 TS 文件。

如果想編譯指定文件,則可以在 compilerOptions 配置項(xiàng)同級增加:

"include": ["./demo.ts"]或者"files": ["./demo.ts"]

如果想要不包含某個(gè)文件,則可以同上增加:

"exclude": ["./demo.ts"]

有關(guān)于這一塊的更多配置,可以參考 tsconfig 配置文檔。

下面再來關(guān)注下 compilerOptions 中的屬性,由這個(gè)英文名就知道,這其實(shí)就是指的編譯配置的意思。

"compilerOptions": {     "increments": true                          // 增量編譯,只編譯新增加的內(nèi)容     "target": "es5",                            // 指定 ECMAScript 目標(biāo)版本: 'ES5'     "module": "commonjs",                       // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'     "moduleResolution": "node",                 // 選擇模塊解析策略     "experimentalDecorators": true,             // 啟用實(shí)驗(yàn)性的ES裝飾器     "allowSyntheticDefaultImports": true,       // 允許從沒有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入。     "sourceMap": true,                          // 把 ts 文件編譯成 js 文件的時(shí)候,同時(shí)生成對應(yīng)的 map 文件     "strict": true,                             // 啟用所有嚴(yán)格類型檢查選項(xiàng)     "noImplicitAny": true,                      // 在表達(dá)式和聲明上有隱含的 any類型時(shí)報(bào)錯(cuò)     "alwaysStrict": true,                       // 以嚴(yán)格模式檢查模塊,并在每個(gè)文件里加入 'use strict'     "declaration": true,                        // 生成相應(yīng)的.d.ts文件     "removeComments": true,                     // 刪除編譯后的所有的注釋     "noImplicitReturns": true,                  // 不是函數(shù)的所有返回路徑都有返回值時(shí)報(bào)錯(cuò)     "importHelpers": true,                      // 從 tslib 導(dǎo)入輔助工具函數(shù)     "lib": ["es6", "dom"],                      // 指定要包含在編譯中的庫文件     "typeRoots": ["node_modules/@types"],     "outDir": "./dist",                         // 生成文件目錄     "rootDir": "./src"                          // 入口文件   },

接口(interface)

接口是用來自定義類型或者為我們的第三方 JS  庫做翻譯的一種方式。之前的代碼中已經(jīng)使用到了接口,其實(shí)就是用來描述類型的。每個(gè)人都有姓名和年齡,那我們就會這樣去約束 person。

interface Person {     name: string     age: number }let person: Person

當(dāng)我們進(jìn)行這樣的類型約束的時(shí)候,person 這個(gè)對象在初始化的時(shí)候就必須要有 name 和 age,初始化有兩種方式,再來看下其中的不同支出:

// 承接上面的代碼 // 第一種初始化方式 person = {     name: 'aaa',     age: 18 } // 第二種初始化方式 let p = {     name: 'aaa',     age: 18,     sex: 'male' } person = p

第一種方式和第二種方式相比,p 對象中多了一個(gè) sex 屬性,然后賦值給了 person,編輯器沒有提示錯(cuò)誤,但是如果在第一種方式中添加一個(gè) sex  屬性則會報(bào)錯(cuò),這是為什么呢?

這是因?yàn)?,?dāng)我們直接賦值(也就是通過第一種方式)的時(shí)候,TS 會進(jìn)行強(qiáng)類型檢查,因此必須和接口定義的類型一致才行。

注意我們上面提到一致,一致的意思是,屬性名和屬性值類型一致,且屬性個(gè)數(shù)不多不少。而當(dāng)使用第二種方式進(jìn)行賦值的時(shí)候,則會進(jìn)行弱檢查。屬性個(gè)數(shù)一致會較弱,表現(xiàn)在,當(dāng)屬性多了一個(gè)的時(shí)候,不會有語法錯(cuò)誤。

此時(shí)我們會產(chǎn)生一個(gè)疑問,如果我們想讓第一種方式也能做到和第二種方式一樣,或者說,每個(gè)人年齡和姓名是必須的,但是所在城市 city  是選填的,那該如何呢?我們可以用可選屬性描述。

interface Person {     name: string     age: number     city?: string }

如果這樣的話,我們在調(diào)用 p 屬性的時(shí)候就可以看到 city 屬性可能是 string,也可能是 undefined:

有哪些關(guān)于TypeScript的知識點(diǎn)

不僅如此,我們還希望,age 屬性是不可修改的,readonly 屬性自然就派上用場了,當(dāng)你試圖修改定義了 readonly  屬性的時(shí)候,那么編輯器就會發(fā)出警告:

interface Person {     name: string     readonly age: number     city?: string }let person: Person = {     name: 'aaa',     age: 18 }// person.age = 18
有哪些關(guān)于TypeScript的知識點(diǎn)

當(dāng)然這還沒結(jié)束,如果有一天,還想再擴(kuò)展一個(gè)接口,是公司職員的接口,但是職員接口類肯定有 Person 類的所有信息,再擴(kuò)展一個(gè)  id,又該如何呢?這時(shí)候繼承(extends)就上場了。

interface Employee extends Person {     id: number }

接口還可以用來約束類,讓定義的類必須有某種屬性或者方法,這時(shí)候關(guān)鍵字就不是 extends,而是 implements。

interface User {   name: string  getName(): string}class Student implements User {   name = 'aaa'   getName() {    return this.name   }}

interface VS type

interface 和 type 作用看起來似乎是差不多的,都是用來定義類型,接下讓我們看下它的相同點(diǎn)與不同點(diǎn)。

相同點(diǎn):

1. 都可以描述對象或函數(shù)

interface Person {   name: string   age: number}type Person1 = {  //type 定義類型有等號   name: string   age: number}interface getResult {  (value: string): void }type getResult1 = (value: string): void

2. 都可以實(shí)現(xiàn)繼承

// interface 繼承 interface interface People extends Person {  sex: string }// interface 繼承 typeinterface People extends Person1 {  sex: string }// type 繼承 type type People1 = Person1 & {  sex: string }// type 繼承 interface type People1 =  Person & {  sex: string }

不同點(diǎn):

1. type 可以聲明基本類型、聯(lián)合類型,interface 不行

// 基本類型   type Person = string // 聯(lián)合類型type User = Person | number

2. interface 可以類型合并

interface People {   name: string   age: number }interface People {   sex: string }//People 最終類型為 {   name: string   age: number   sex: string }

3. interface 可以定義可選和只讀屬性(之前講過,這里不再贅述)

接口的基礎(chǔ)知識差不多就介紹完了,當(dāng)然接口在實(shí)際開發(fā)場景中應(yīng)用會更復(fù)雜,如果你還有很多疑惑,接著往下看,下面的講解將會解答你的疑惑。

聯(lián)合類型和類型保護(hù)

和其他分享資料不同,我希望每一個(gè)知識點(diǎn)都能先讓你先有所疑惑,啟發(fā)你的思考,然后我再慢慢解決你的疑惑,這樣我相信你會記憶更加深刻,否則可能將成效見微。

閑話少敘,直接上一段代碼:

interface Bird {   fly: boolean   sing: () => {} }interface Dog {   fly: boolean   bark: () => {} }function trainAnimal(animal: Bird | Dog) {   // animal.sing() }

上面代碼中我定義了兩個(gè)類型,一個(gè) Bird 類型,一個(gè)是 Dog 類型。函數(shù) trainAnimal 的形參接收一個(gè) animal 的參數(shù),這個(gè)參數(shù)可能是  Bird 類型,也可能是 Dog 類型,這就是聯(lián)合類型。當(dāng)在函數(shù)中調(diào)用的時(shí)候,編輯器給的提示只有 fly:

有哪些關(guān)于TypeScript的知識點(diǎn)

這還真有點(diǎn)東西,但是仔細(xì)想想,就覺得只有 fly 沒毛病。因?yàn)槁?lián)合類型的 animal  無法確定具體是哪個(gè)類型,因此只能提示共有的屬性。而獨(dú)有方法經(jīng)過聯(lián)合類型阻隔之后是無法進(jìn)行語法提示。如果我們強(qiáng)行調(diào)用某個(gè)類型獨(dú)有的方法,可以看到編輯器會有錯(cuò)誤提示。

有哪些關(guān)于TypeScript的知識點(diǎn)

如果確實(shí)需要使用獨(dú)有方法,該當(dāng)如何?

這就需要類型保護(hù)了,確實(shí),如果聯(lián)合類型只能調(diào)用共有方法,似乎看起來也用處不是很大,好在有類型保護(hù)。類型保護(hù)也有好多種,我們分別來介紹下。

1. 類型斷言

function trainAnimal(animal: Bird | Dog) {     if (animal.fly) {         (animal as Bird).sing()     } else {         (animal as Dog).bark()     }}

上面代碼中通過一個(gè) as 關(guān)鍵字實(shí)現(xiàn)了類型斷言。因?yàn)榘凑者壿?,我們知道,如果?fly 方法,那么 animal 一定是 Bird  類型,但是編輯器不知道,所以通過 as 告訴編輯器此時(shí) animal 就是 Bird 類型,Dog 類型的確定也是同理。

2. 通過 in 來類型斷言,TS 語法檢查就能確定參數(shù)類型

function trainAnimalSecond(anmal: Bird | Dog ) {     if ('sing' in animal) {         animal.sing()     }}

3. 通過 typeof 來做類型保護(hù)

function add(first: string | number, second: string | number) {     if (typeof first === 'string' || typeof second === 'string') {         return `first:${first}second:${second}`     }    return first + second }

上面代碼中如何沒有 if 里面的邏輯,直接進(jìn)行判斷,編輯器則會給錯(cuò),因?yàn)槿绻菙?shù)字和字符串相加,則可能存在錯(cuò)誤,因此通過 typeof 來確定,當(dāng)  first 和 second 都是數(shù)字的時(shí)候,進(jìn)行相加。

4. 通過 instanceof 來類型保護(hù)

class NumberObj {     count: number}function addSecond(first: object | NumberObj, second: object | NumberObj) {     if (first instanceof NumberObj && second instanceof NumberObj) {         return first.count + second.count     }}

在 TS  中,類不僅可以用來實(shí)例化對象,也可以用來定義變量類型,當(dāng)一個(gè)對象被一個(gè)類定義以后,表明這個(gè)對象的值就是這個(gè)類的實(shí)例,關(guān)于類這一塊的寫法有疑問,可以查閱下 ES7  相關(guān)內(nèi)容,這里不做過多講解。

從代碼中我們可以看出,通過 instanceof 來確定具有聯(lián)合類型的形參是否是類的類型,當(dāng)然這里如果要用 instanceof  來判斷,我們的自定義類型定義只能用 class。如果是 interface 定義的類型,使用 instanceof 則會報(bào)錯(cuò)。

枚舉類型

枚舉這個(gè)概念,我們在 JS 中就已經(jīng)接觸的比較多了,關(guān)于概念也不就不做過多的講解,直接上一段代碼。

const Status = {     OFFLINE: 0,     ONLINE: 1,     DELETED: 2 }function getStatus(status) {     if (status == Status.OFFLINE) {         return 'offline'     } else if (status == Status.ONLINE) {         return 'online'     } else if (status == Status.DELETED) {         return 'deleted'     }    return error }

這是我們在 JS 中比較常見的寫法,TS 中也有枚舉類型,而且比 JS 的更好用。

enum Status {     OFFLINE,    ONLINE,    DELETED}// 方式一 const status = Status.OFFLINE  // 0 // 方式二 const status = Status[0]  // OFFLINE

通過上面的代碼可以看出,TS 的枚舉類型默認(rèn)會有賦值,而且寫法也很簡單。再看方式一和方式二對枚舉類型的使用,我們可以看出,TS  枚舉類型還支持正反調(diào)用。

剛才說到枚舉類型默認(rèn)有值,如果我想改默認(rèn)值又該如何呢?請看下面的代碼:

enum Status {     OFFLINE = 3,     ONLINE,    DELETED}const status = Status.OFFLINE  // 3 const status = Status.ONLINE  // 4 enum Status1 {    OFFLINE = 6,     ONLINE = 10,     DELETED}const status = Status.OFFLINE  // 6 const status = Status.ONLINE  // 10 const status = Status.DELETED  // 11

由上可以看出,TS 枚舉類型支持自定義值,且后面的枚舉屬性沒有賦值的話,會在原來的基礎(chǔ)上遞增。

上面我們說到 enum 支持雙向使用,為什么它如此之秀,怎么靈活呢,我們看下枚舉類型編譯成 JS 后的代碼:

var Status; (function (Status) {     Status[Status["OFFLINE"] = 6] = "OFFLINE";     Status[Status["ONLINE"] = 10] = "ONLINE";     Status[Status["DELETED"] = 12] = "DELETED"; })(Status || (Status = {}))

函數(shù)泛型

泛型在 TS 的開發(fā)中使用非常廣泛,因此這一節(jié),同樣會由淺入深,先看代碼:

function result(first: string | number, second: string | number) {     return `${first} + ${second}` }join('1', 1) join(1,'1')

這是我們之前講過的聯(lián)合類型,兩個(gè)參數(shù)既可以是數(shù)字也可以字符串。

但是現(xiàn)在我有個(gè)需求是這樣的,如果 first 是字符串,則 second 只能是字符串,同理 first 是數(shù)字,則  second。如果不知道泛型,我們只能在函數(shù)內(nèi)部去進(jìn)行邏輯約定,但是泛型一出手,問題就迎刃而解。

function result<T>(first: T,second: T) {     return `${first} + ${second}` }join<number>(1,1) join<string>('1','1')

通過在函數(shù)中定義一個(gè)泛型 T(名字可以自定義,一般用 T),這樣的話,我們就可以約束 first,second  類型一致,當(dāng)我們試圖調(diào)用的時(shí)候?qū)崊㈩愋筒灰恢碌臅r(shí)候,那么編輯器就會報(bào)錯(cuò)。

function map<T>(params: T[]) {     return params }map([1])

這種形式也是可以的,雖然調(diào)用的時(shí)候沒有顯示定義 T,但是 TS 可以推斷出 T 的類型。T[] 是數(shù)組一種定義類型的方式,表明數(shù)組每個(gè)值的類型。

注意:Array 這種形式在 3.4 之后,會有警告。統(tǒng)一使用方括號形式。

這是單一泛型,但實(shí)際場景中往往是多個(gè)泛型:

function result<T, U>(first: T,second: U) {     return `${first} + ${second}` }join<number,string>(1,'1') join(1, '1')  //這種形式也可

泛型如此之好用,肯定不可能只在函數(shù)中使用,因此接下來再來說下類中使用泛型:

class DataManager {   constructor(private data: string[] | number[]) {}   getItem(index: number): string | number {     return this.data[index]   }}const data = new DataManager([1]) data.getItem(0)

DataManager 類中構(gòu)造函數(shù)通過聯(lián)合類型來定義 data  的類型,這在復(fù)雜的業(yè)務(wù)場景中顯然是不可取的,因?yàn)槿绻覀円膊淮_定類型,在傳參之前,那么只能寫更多的類型或者定義成 any  類型,這就顯得很不靈活,這時(shí)候我們想到了泛型,是否可以應(yīng)用到類中呢?

答案是肯定的。

class DataManager<T> {   constructor(private data: <T>) {}   getItem(index: number): <T> {    return this.data[index]   }}const data = new DataManager([1]) // const data = new DataManager<number>([1])  //直觀的寫法,和上面等價(jià) data.getItem(0)

看起來好像已經(jīng)很靈活了,但是還有一個(gè)問題,沒有規(guī)矩不成方圓,函數(shù)編寫者允許調(diào)用者具有傳參靈活度,但是需要符合函數(shù)內(nèi)部的一些邏輯,也就是說之前函數(shù)  return this.data[index],但是現(xiàn)在函數(shù)邏輯里面,返回的是 this.data[index].name,也就是函數(shù)調(diào)用者可以傳 T  類型進(jìn)來,但是每一項(xiàng)必須要有 name 屬性,這又該當(dāng)如何?

那么我們可以再定義一個(gè)接口,讓 T 繼承接口,這樣既能保持靈活度,又能符合函數(shù)邏輯。

interface Item {     name: string }class DataManager<T extends Item> {     constructor(private data: T[]) {}     getItem(index: number): number {         return this.data[index].name     }}const data = new DataManager([     name: 'dell' ])

講到這里,泛型差不多結(jié)束了,但是還有一個(gè)疑問,上面number | string 這種聯(lián)合類型想用泛型來約束,該怎么寫呢,也就是 T 只能是 string  或者 number。

class DataManager<T extends number | string> {     constructor(private data: T[]) {}     getItem(index: number): T {         return this.data[index]     }}

命名空間

講到這里,我們之前已經(jīng)新建了很多的 demo 文件,不知道你有沒有發(fā)現(xiàn)這樣一個(gè)奇怪的現(xiàn)象。

demo.ts

let a = 123// dosomething

demo1.ts

let a = '123'

當(dāng)我們在 demo1.ts 文件中再去定義 a 這個(gè)變量的時(shí)候,a 會標(biāo)紅,告訴我們 a 已經(jīng)被聲明了 number 類型,這是為什么呢?

我們明明在 demo1.ts 文件中沒有定義過 a,再仔細(xì)看下提示,它告訴我們已經(jīng)在 demo.ts 中定義過了。對 JS  很熟練的伙伴一定知道了,應(yīng)該是模塊化的問題。

沒錯(cuò),TS 跟 JS 一樣,一個(gè)文件中不帶有頂級的 import 或者 export 聲明,它的內(nèi)容是全局可見的,換句話說,如果我們文件中帶有  import 或者 export,則是一個(gè)模塊化。

export const let a = '123'

這樣就沒有問題了,我們再看下下面這段代碼:

class A {   // do something } class B {   // do something } class C {   // do something } class D {   constructor() {     new A()     new B()     new C()   } }

代碼中,我定義了四個(gè)類,上面提到,如果我把 D 這個(gè)類通過 export 導(dǎo)出,這樣其他文件中就可以繼續(xù)使用 A  或者其他幾個(gè)類名了,但是我現(xiàn)在有個(gè)需求是這樣的,我不想把 A、B、C 三個(gè)類暴露出去,而且在外面能不能通過想通過對象的方式去調(diào)用 D 這個(gè)類。namespace  登場,看下代碼:

namespace Total{   class A {     // do something   }   class B {     // do something   }   class C {     // do something   }   export class D {     constructor() {       new A()       new B()       new C()     }   } } Total.D

這樣寫就可以了,通過 namespace 就只能調(diào)用到 D。如果還想調(diào)用其他類,只需要在前面去 export 這個(gè)類就好了。

namespace 在實(shí)際開發(fā)中,我們一般用在寫一些 .d.ts 文件。也就是 JS 解釋文件。

命名空間本質(zhì)上是一個(gè)對象,它的作用就是將一系列相關(guān)的全局變量變成一個(gè)對象的屬性,再看下上面的代碼編譯成 JS 是怎么樣的。

var Total; (function (Total) {     var A = /** @class */ (function () {         function A() {         }        return A;     }());    var B = /** @class */ (function () {         function B() {         }        return B;     }());    var C = /** @class */ (function () {         function C() {         }        return C;     }());    var D = /** @class */ (function () {         function D() {             new A();             new B();             new C();         }        return D;     }());    Total.D = D;})(Total || (Total = {}));Total.D;

從上面可以看出,通過一個(gè)立即執(zhí)行函數(shù)并且傳了一個(gè)變量進(jìn)去,然后把導(dǎo)出的方法掛載在變量上,這樣就可以在外面通過對象屬性的方式調(diào)用類。

最后再補(bǔ)充下 declare,它的作用是,為第三方 JS 庫編寫聲明文件,這樣才可以獲得對應(yīng)的代碼補(bǔ)全和接口提示:

//常用的聲明類型   declare function 聲明全局方法 declare class 聲明全局類 declare enum 聲明全局枚舉類型 declare global 擴(kuò)展全局變量 declare module 擴(kuò)展模塊

也可以使用 declare 做模塊補(bǔ)充。下面摘自官方的一個(gè)示例:

// observable.ts export class Observable<T> {    // ... implementation left as an exercise for the reader ... }// map.tsimport { Observable } from "./observable"; declare module "./observable" {     interface Observable<T> {        map<U>(f: (x: T) => U): Observable<U>;    }}Observable.prototype.map = function (f) {     // ... another exercise for the reader }// consumer.tsimport { Observable } from "./observable"; import "./map"; let o: Observable<number>; o.map(x => x.toFixed());

代碼的意思在 map.js 中定制一個(gè)文件,補(bǔ)充你想要的類型 map 方法并實(shí)現(xiàn)函數(shù)掛載在 Observable 原型上,然后在 consumer.ts  就可以使用 Observable 類型里面的 map。

TypeScript 高級語法

類的裝飾器

裝飾器我們在 JS 就已經(jīng)接觸比較久了,并且在我的另一篇  Chat《寫給前端同學(xué)容易理解并掌握的設(shè)計(jì)模式》中也詳細(xì)講解了裝飾器模式,對設(shè)計(jì)模式感興趣的同學(xué),歡迎訂閱。裝飾器本質(zhì)上就是一個(gè)函數(shù)。@description  這種語法其實(shí)就是一個(gè)語法糖。TS 和 JS 裝飾器使用大同小異,先看一個(gè)簡單的例子:

function Decorator(constructor: any) {     console.log('decorator') }@Decorator class Demo{} const text = new Test()

當(dāng)我們覺得完美的時(shí)候,編輯器給了我們一個(gè)標(biāo)紅:

有哪些關(guān)于TypeScript的知識點(diǎn)

其實(shí)裝飾器是一個(gè)實(shí)驗(yàn)性質(zhì)的語法,所以不能直接使用,需要打開實(shí)驗(yàn)支持,修改 tsconfig 的以下兩個(gè)選項(xiàng):

"experimentalDecorators": true, "emitDecoratorMetadata": true,

修改完配置之后,就發(fā)現(xiàn)終端正確輸出了。

但是這里我還要再拋出一個(gè)問題,裝飾器的運(yùn)行時(shí)機(jī)是什么時(shí)候呢,是在類實(shí)例化的時(shí)候嗎?

其實(shí)裝飾器在類創(chuàng)建的時(shí)候就已經(jīng)運(yùn)行裝飾器了,可以自行注釋掉實(shí)例化語句,再運(yùn)行,看控制臺是否有 log。

類的裝飾器修飾函數(shù)接受的參數(shù)是類的構(gòu)造函數(shù),我們可以改一下 Decorator 來驗(yàn)證一下:

function Decorator(constructor: any) {     constructor.prototype.getResult = () => {         console.log('constructor')     }}@Decorator class Demo{} const text = new Test() text.getResult()

控制臺正確打印出 constructor  就可以證明接收的參數(shù)確實(shí)是類的構(gòu)造函數(shù)。上面的代碼中我們只在類中使用了一個(gè)裝飾器,但其實(shí)可以給一個(gè)類使用多個(gè)裝飾器,寫法如下:

@Decorator @Decorator1 class Demo{}

多個(gè)裝飾器執(zhí)行順序?yàn)橄认潞笊稀?/p>

上面的裝飾器寫法,我們把整個(gè)函數(shù)都給了類做裝飾,但是實(shí)際情況是,我函數(shù)有一些邏輯,是不給類裝飾使用的,那么我們寫成一個(gè)工廠模式去給類裝飾:

function Decorator() {     // do something     return function (constructor: any) {         console.log('descorator')     }}@Decorator()class Test()

通過這樣,我們可以傳一些參數(shù)進(jìn)去,然后函數(shù)內(nèi)部去控制裝飾器的裝飾。

不知道你有沒有發(fā)現(xiàn),我們在驗(yàn)證裝飾器參數(shù)的時(shí)候,當(dāng)我們通過類的實(shí)例去調(diào)用我們掛載在裝飾器原型的方法的時(shí)候,雖然沒有報(bào)錯(cuò),但是編輯器沒有給我們提示,這是很不符合我們預(yù)期的。上面那種裝飾器寫法很簡單,但很直觀。

但在 TS 中我們往往是像下面這種方式使用的,而且也能解決上面提到的那個(gè)問題:

function Decorator() {     return function <T extends new (...args: any[]) => any>(constructor: T) {         return class extends constructor{             name = 'bbb'             getName        }    }} const Test = Decorator()( class {     name: string     constructor(name: string) {         console.log(this.name,'1')         this.name = name         console.log(this.name,'2')     }})const test = new Test('aaa') console.log(test.getName())

我們把之前的代碼大變樣,看起來似乎高大上了許多,但是理解起來也挺有難度的。別急,讓我來一一進(jìn)行解釋。

<T extends new (...args: any[]) => any>

這個(gè)是一個(gè)泛型,T 繼承了一個(gè)構(gòu)造函數(shù)也可以說是繼承了一個(gè)類,構(gòu)造函數(shù)參數(shù)是一個(gè)展開運(yùn)算符,表示接收多個(gè)參數(shù)。

這樣泛型 T 就可以用來定義 constructor。而 Decorator  函數(shù),跟上面一樣,我們寫成函數(shù)柯里化形式,并且把類作為參數(shù)傳遞進(jìn)去,摒棄了之前的語法糖,這樣我們在調(diào)用裝飾在類上的方法的時(shí)候編輯器就能給我們提示。

方法裝飾器

上一節(jié),分享完了類的裝飾器,大家肯定對裝飾器意猶未盡,這一小節(jié),再分享下給類的方法裝飾,先上個(gè)代碼,來看下:

function getNameDecorator(   target: any,   key: string,   descriptor: PropertyDescriptor ) {   console.log(target); } class Test {      name: string      constructor(name: string) {          this.name = name      }     @getNameDecorator      getName() {         return this.name      } }const test - new Test('aaa') console.log(test.getName())

這就實(shí)現(xiàn)了給類的方法進(jìn)行裝飾,當(dāng)我們給類的普通方法進(jìn)行裝飾的時(shí)候,裝飾器函數(shù)中接收的參數(shù) target 對應(yīng)的是類的 prototype,key  是裝飾的普通方法的名字。

注意,我上面說的是普通方法。和類的裝飾器一樣,方法裝飾器的執(zhí)行時(shí)機(jī)同樣是當(dāng)方法被定義的時(shí)候。

剛才我已經(jīng)強(qiáng)調(diào)了普通方法,接下來我就要說靜態(tài)方法了。

class Test {     name: string     constructor(name: string) {         this.name = name     }    @getNameDecorator     static getName() {         return this.name     }}

靜態(tài)方法的裝飾器函數(shù)中,第一個(gè)參數(shù) target 對應(yīng)的是類的構(gòu)造函數(shù)。

類的方法裝飾器函數(shù)中,我們還有一個(gè)參數(shù)沒有講,那就是 descriptor。

不知道你有沒有發(fā)現(xiàn),這個(gè)函數(shù)接收三個(gè)參數(shù),而且第三個(gè)參數(shù)還是 descriptor,有點(diǎn)像 Object.defineProperty 這個(gè)  API,當(dāng)我們在函數(shù)中調(diào)用 descriptor 的時(shí)候,編輯器會給我們提示。

這幾個(gè)屬性和 Object.defineProperty 中的 descriptor  可設(shè)置屬性一樣,沒錯(cuò),功能也是一樣的.比如,我們不想在外部,getName 方法被重寫,那么我們可以這樣:

function getNameDecorator(   target: any,   key: string,   descriptor: PropertyDescriptor ) {   console.log(target);   descriptor.writable = false }

當(dāng)你試圖這樣去修改它的時(shí)候,運(yùn)行編譯后文件將會報(bào)錯(cuò):

const test = new Test('aaa') console.log(test.getName()) test.getName = () => {     return 'aaa' }

這是運(yùn)行結(jié)果:

有哪些關(guān)于TypeScript的知識點(diǎn)

訪問器裝飾器

在 ES6 的 class 中新增訪問器,通過 get 和 set 方法訪問屬性,如果上面的知識點(diǎn)你都消化了,那么訪問器裝飾器的用法也是如出一轍。

function visitDecorator(     target: any,     key: string,     descriptor: PropertyDescriptor ){} class Test {     provate _name: string     constructor(name: string) {         this._name = name     }    get name() {         return this._name     }    @visitDecorator     set name() {         this._name = name     }}

訪問器裝飾器的用法跟類的普通方法裝飾器用法差不多,這里就不展開來講了。同樣地,在類中,我們也可以給屬性添加裝飾器,參數(shù)添加裝飾器。

裝飾器業(yè)務(wù)場景使用

之前我們花了比較長的篇幅來介紹裝飾器,這一小節(jié),將跟大家分享下實(shí)際業(yè)務(wù)場景中,裝飾器的使用。首先來看這樣一段代碼:

const uerInfo: any = undefined class Test {    getName() {        return userInfo.name     }    getAge() {        return userInfo.name     }}const test = new Test() test.getName()

這段代碼不用運(yùn)行,我們都能知道,會報(bào)錯(cuò),因?yàn)?userInfo 沒有 name 屬性。因此如果我們想要不報(bào)錯(cuò),就會寫成這樣:

class Test {     getName() {        try {             return userInfo.name         } catch (e) {             console.log('userInfo.name 不存在')         }    }    getAge() {        try {             return userInfo.age         } catch (e) {             console.log('userInfo.age 不存在')         }    }}

把類改成這樣,似乎就沒有問題了,為什么說似乎呢?

那是因?yàn)檫\(yùn)行雖然沒有問題,但是如果我們還有很多類似于這樣的方法,我們是否要重復(fù)處理錯(cuò)誤呢?能否用到之前講的裝飾器來處理錯(cuò)誤:

const userInfo: any = undefined function catchError(    target: any,     key: string,     descriptor: PropertyDescriptor){    const fn = descriptor.value     descriptor.value = function() {         try {             fn()        } catch (e) {             console.log('userinfo 出問題啦')         }    }}class Test {     @catchError     getName() {        return userInfo.name     }    @catchError     getAge() {        return userInfo.age     }}

這樣我們就把捕獲異常的邏輯提取出來了,通過裝飾器來復(fù)用。

但是和我們之前寫的還有點(diǎn)差異,就是報(bào)錯(cuò)信息都一樣,我們不知道具體是哪個(gè)函數(shù)報(bào)的錯(cuò),也就是說,我們希望裝飾器函數(shù)可以接收一個(gè)參數(shù),來完善報(bào)錯(cuò)信息,這樣的話,我們就可以用到講過的,把裝飾器包裝成一個(gè)工廠函數(shù),代碼如下:

function catchError(msg: string) {     return function (         target: any,         key: string,         descriptor: PropertyDescriptor     ){         const fn = descriptor.value         descriptor.value = function() {             try {                 fn()            } catch (e) {                 console.log(`userinfo.${msg} 出問題啦`)             }        }    }}class Test {     @catchError('name')     getName() {        return userInfo.name     }    @catchError('age)'     getAge() {        return userInfo.age     }}

這樣我們的代碼就能滿足我們的需求了,后面我們再添加其他函數(shù)函數(shù),也可以用裝飾器對其進(jìn)行裝飾。

項(xiàng)目中應(yīng)用 TypeScript

腳手架搭建一個(gè) TypeScript

現(xiàn)在的開發(fā)越來越專業(yè),一般我們初始化一個(gè)項(xiàng)目,如果不用腳手架進(jìn)行開發(fā)的話,需要自己去配置一大堆東西,比如  package.json、.gitignore,還有一些構(gòu)建工具,像 webpack 等以及他們的配置。

而當(dāng)我們?nèi)ナ褂?TypeScript 編寫一個(gè)項(xiàng)目的時(shí)候,還需要配置 TypeScript 的編譯配置文件 tsconfig 以及 tslint.json  文件。

如果我們只是想做一個(gè)小項(xiàng)目或者只想學(xué)習(xí)這塊的開發(fā),那前期的磨刀準(zhǔn)備工作將讓很多人望而卻步,一頭霧水。因此,一個(gè)腳手架工具就可以幫我們把刀磨好,而且磨的錚鮮亮麗的,這個(gè)工具就是  TypeScript Library Starter。讓我們一起來了解下。

查看它的官網(wǎng),我們知道這是一個(gè)以 TypeScript 為基礎(chǔ)的開源腳手架工具,幫助我們快速開始一個(gè) TypeScript 項(xiàng)目,使用方法如下:

git clone https://github.com/alexjoverm/typescript-library-starter.git ts-project cd ts-projectnpm install

這幾行命令的意思是,把代碼拉下來然后給項(xiàng)目重命名。進(jìn)入到項(xiàng)目,通過 npm install 去給項(xiàng)目安裝依賴,然后我們來看下我們的文件目錄:

有哪些關(guān)于TypeScript的知識點(diǎn)

├── package.json  // 項(xiàng)目配置文件 

├── rollup.config.ts // rollup 配置文件 ├── src // 源碼目錄 ├── test // 測試目錄 ├── tools // 發(fā)布到 GitHup pages 以及 發(fā)布到 npm 的一些配置腳本工具 ├── tsconfig.json // TypeScript 編譯配置文件 └── tslint.json // TypeScript lint 文件

TypeScript library starter  創(chuàng)建的項(xiàng)目確實(shí)集成了很多優(yōu)秀的開源工具,包括打包、單元測試、格式化代碼等,有興趣的同學(xué)可以自行深入研究下。

還有需要介紹的是,TypeScript library starter 在 package.json 中幫我們配置了一套完整的前端工作流:

  • npm run lint:使用 TSLint 工具檢查 src 和 test 目錄下 TypeScript 代碼的可讀性、可維護(hù)性和功能性錯(cuò)誤。

  • npm start:觀察者模式運(yùn)行 rollup 工具打包代碼。

  • npm test:運(yùn)行 Jest 工具跑單元測試。

  • npm run commit:運(yùn)行 commitizen 工具提交格式化的 git commit 注釋。

  • npm run build:運(yùn)行 rollup 編譯打包 TypeScript 代碼,并運(yùn)行 typedoc 工具生成文檔。

其他一些命令在我們?nèi)粘i_發(fā)中使用不是非常多,有需要的同學(xué)可以再自行去了解。

TypeScript 實(shí)戰(zhàn)

現(xiàn)在我們的前端項(xiàng)目基本都是使用框架進(jìn)行開發(fā),今天我就介紹如何使用 React + TypeScript 進(jìn)行 React 項(xiàng)目開發(fā)。當(dāng)然這里我們還是會使用  React 提供的腳手架迅速搭建項(xiàng)目框架,為了避免你本地之前的腳手架版本影響 TypeScript 的開發(fā),建議先執(zhí)行:

npm uninstall create-react-app

然后執(zhí)行官方提供的 React TypeScript 生成命令:

npx create-react-app react-project --template typescript --use-npm

這個(gè)命令的意思是下載最新腳手架(如果當(dāng)前環(huán)境沒有這個(gè)腳手架的話),然后通過 create-react-app 腳手架去生成以 typescript  為開發(fā)模板的項(xiàng)目,項(xiàng)目名字叫 react-project,并通過 npm 去安裝依賴,如果沒有 --use-npm 則會默認(rèn)是使用 Yarn。

項(xiàng)目搭建完成之后,我們把文件整理下,刪除一些我們不用的文件,同時(shí)把相關(guān)引用也刪除,最終文件目錄如下:

有哪些關(guān)于TypeScript的知識點(diǎn)

當(dāng)我們使用 TS 去寫 React 的時(shí)候, jsx 就變成了 tsx。在 APP.tsx 文件中:

const App: React.FC = () => {     return <div className="App"></div> }

通過 React.FC 給函數(shù)定義了一個(gè) React.FC 的函數(shù)類型,這是 React 中定義的函數(shù)類型。

前端 UI 開發(fā),現(xiàn)在市面上也有很多封裝好的框架,讓我們可以快速搭建一個(gè)頁面,這里我們選用 ant-design,這個(gè)框架也是使用 TypeScript  進(jìn)行開發(fā)的,所以我們使用它進(jìn)行開發(fā)的時(shí)候,會有很多類型可以供我們使用,因此使用它去鞏固我們剛學(xué)習(xí)的 TypeScript 知識點(diǎn)會有更多的好處。

首先讓我們來安裝下這個(gè)組件庫:

npm install antd --save

安裝好之后,再 index.tsx 中引入 CSS 樣式:

import 'antd/dist/antd.css'

接下來我們?nèi)憘€(gè)登錄頁面,首頁新建一個(gè) login.css:

.login-page {   width: 300px;   padding: 20px;   margin: 100px auto;   border: 1px solid #ccc; }

然后我們?nèi)?antd-design 官網(wǎng),把登錄組件代碼復(fù)制到我們的 App.ts 中:

import React from "react"; // import ReactDOM from 'react-dom' import "./login.css"; // function App() { //   return <div className="login-page">Hello world</div>; // } // export default App; import { Form, Input, Button, Checkbox } from "antd"; // import { Store } from "antd/lib/form/interface"; import { ValidateErrorEntity, Store } from "rc-field-form/lib/interface"; const layout = {  labelCol: {    span: 8,   },  wrapperCol: {    span: 16,   },};const tailLayout = {  wrapperCol: {    offset: 8,     span: 16,   },};const App = () => {   const onFinish = (values: Store) => {     console.log("Success:", values);   };  // const onFinishFailed = (errorInfo: Store) => {   const onFinishFailed = (errorInfo: ValidateErrorEntity) => {     console.log("Failed:", errorInfo);   };  return (     <div className="login-page">       <Form        {...layout}        name="basic"         initialValues={{          remember: true,         }}        onFinish={onFinish}        onFinishFailed={onFinishFailed}      >        <Form.Item          label="Username"           name="username"           rules={[            {              required: true,               message: "Please input your username!",             },          ]}        >          <Input />         </Form.Item>         <Form.Item           label="Password"           name="password"           rules={[             {               required: true,               message: "Please input your password!",             },           ]}         >           <Input.Password />         </Form.Item>         <Form.Item {...tailLayout} name="remember" valuePropName="checked">           <Checkbox>Remember me</Checkbox>         </Form.Item>         <Form.Item {...tailLayout}>           <Button type="primary" htmlType="submit">             Submit           </Button>         </Form.Item>       </Form>     </div>   ); }; // ReactDOM.render(<Demo />, mountNode); export default App;

其中,onFinish 函數(shù)的 values 編輯器給我們報(bào)隱患提示,我們也無法確定 value 的類型,但是又不能填寫 any。因此,我們可以去找下  Form 中定義的類型。mac 用戶把鼠標(biāo)放在 import 中的 From 標(biāo)簽上( windows 用戶按住  cmd),進(jìn)入到源代碼中去,然后一直去查找我們的方法的定義,首先我們進(jìn)入到了:

有哪些關(guān)于TypeScript的知識點(diǎn)

然后 InternalForm 繼承了 InternalForm,我們再繼續(xù)去尋找,最后找到了源頭:

有哪些關(guān)于TypeScript的知識點(diǎn)

同理我們也可以找到 onFinishFailed:

有哪些關(guān)于TypeScript的知識點(diǎn)

最后在文件中引入這兩個(gè)類型即可。

經(jīng)過上面的測試之后,我們的項(xiàng)目基本上就算已經(jīng)搭建好了,接下來就可以繼續(xù)充實(shí)相關(guān)的頁面了。

這里再把文件整理下,把不需要的刪除,src 目錄下新建一個(gè) pages 的目錄,然后我們的頁面組件都放在這里,把 login  的代碼也在這個(gè)文件夾下新建一個(gè)文件存放,然后我們再修改下 App.ts:

import { Route, HashRouter, Switch } from "react-router-dom"; import React from "react"; import LoginPage from "./pages/login"; import Home from "./pages/home"; function App() {   return (     <div>       <HashRouter>         <Switch>         <Route path="/" exact component={Home}></Route>           <Route path="/login" exact component={LoginPage}></Route>         </Switch>       </HashRouter>     </div>   );}export default App;

由于 react-router-dom 是 JS 編寫的文件,因此需要再安裝一個(gè)類型定義文件:

npm install @types/react-router-dom -D

到此,關(guān)于“有哪些關(guān)于TypeScript的知識點(diǎn)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI