中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

有哪些關于TypeScript的知識點

發布時間:2021-10-28 15:42:18 來源:億速云 閱讀:199 作者:iii 欄目:web開發

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

開始 TypeScript

學習準備

開始 TypeScript(以下簡稱 TS)正式學習之前,推薦做好以下準備:

  • Node 版本 > 8.0

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

有哪些關于TypeScript的知識點

初識 TypeScript

打開 TypeScript 官網可以看到官方對 TS 的定義是這樣的

JavaScript and More A Result You Can TrustGradual Adoption

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

這是一個 .js 文件代碼:

let a = 123a = '123'

這是 .ts 文件代碼:

let b = 123b = '123'

當我們在 TS 文件中試圖重新給 b 賦值的時候,發生了錯誤,鼠標移動到標紅處,系統提示:

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

原因是什么呢?

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

TypeScript 的優勢

  • TS 靜態類型,可以讓我們在開發過程中發現問題

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

  • 代碼語義清晰易懂,協作更方便

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

先上最熟悉的 JS:

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

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

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

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

TypeScript 環境搭建

搭建 TypeScript 環境,可以直接在終端執行命令:

npm install -g typescript

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

tsc demo.ts

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

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

npm install -g ts-node

這樣我們可以直接運行:

ts-node demo.ts

來運行 TS 文件,如果要初始化 ts 文件夾,進行 TS 相關配置,可以運行:

tsc --init

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

再理解下 TypeScript 中的 Type

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

const a: number = 123

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

有哪些關于TypeScript的知識點

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

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 也分為基礎類型和引用類型。

原始類型

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

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

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

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

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

any 指的是任意類型,在實際開發中應該盡量不要將對象定義為 any 類型:

let a: any = 4 a = '4'

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

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

引用類型

對象類型:賦值時,內必須有定義的對象屬性和方法

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

數組類型:數組中每一項都是定義的類型。

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

類類型:可以先不關注寫法,后面還會詳細講解。

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

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

TypeScript 類型注解和推斷

之前已經講過 TypeScript 的類型和它的類型種類,這一小節還是想繼續把有關類型的知識講全,那么就是類型注解和類型推斷。

類型注解

let a: number a = 123

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

let b = 123

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

簡單的情況,TS 是可以自動分析出類型,但是復雜的情況,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,且函數的返回值也是推斷為 any,因為這種情況下,TS  無法判斷類型,傳參的時候可能傳 number 或者 string 等。

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

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

補充:函數結構中的類型注解。

// 情況一  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 進階

配置文件

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

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

為了驗證下 tsconfig 文件確實會對 TS 文件編譯做配置,修改里面的:

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

然后新建一個 demo.ts 文件:

// 這是一個注釋 const a: number = 123

執行 tsc demo.ts,打開 demo.js 文件,發現注釋并沒有被移除,這是怎么回事,配置文件不生效?

真相是這樣的,當我們直接執行文件的時候,并不會使用 tsconfig 中的配置,只有我們直接執行 tsc,就會使用 tsconfig 中的配置,直接運行  tsc,你就發現了,amazing!

當運行 tsc 命令的時候,直接會先去找到 tsconfig 配置文件,如果沒有做其他改動,會默認編譯根目錄下的 TS 文件。

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

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

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

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

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

下面再來關注下 compilerOptions 中的屬性,由這個英文名就知道,這其實就是指的編譯配置的意思。

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

接口(interface)

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

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

當我們進行這樣的類型約束的時候,person 這個對象在初始化的時候就必須要有 name 和 age,初始化有兩種方式,再來看下其中的不同支出:

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

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

這是因為,當我們直接賦值(也就是通過第一種方式)的時候,TS 會進行強類型檢查,因此必須和接口定義的類型一致才行。

注意我們上面提到一致,一致的意思是,屬性名和屬性值類型一致,且屬性個數不多不少。而當使用第二種方式進行賦值的時候,則會進行弱檢查。屬性個數一致會較弱,表現在,當屬性多了一個的時候,不會有語法錯誤。

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

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

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

有哪些關于TypeScript的知識點

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

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

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

interface Employee extends Person {     id: number }

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

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

interface VS type

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

相同點:

1. 都可以描述對象或函數

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

2. 都可以實現繼承

// 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 }

不同點:

1. type 可以聲明基本類型、聯合類型,interface 不行

// 基本類型   type Person = string // 聯合類型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 可以定義可選和只讀屬性(之前講過,這里不再贅述)

接口的基礎知識差不多就介紹完了,當然接口在實際開發場景中應用會更復雜,如果你還有很多疑惑,接著往下看,下面的講解將會解答你的疑惑。

聯合類型和類型保護

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

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

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

上面代碼中我定義了兩個類型,一個 Bird 類型,一個是 Dog 類型。函數 trainAnimal 的形參接收一個 animal 的參數,這個參數可能是  Bird 類型,也可能是 Dog 類型,這就是聯合類型。當在函數中調用的時候,編輯器給的提示只有 fly:

有哪些關于TypeScript的知識點

這還真有點東西,但是仔細想想,就覺得只有 fly 沒毛病。因為聯合類型的 animal  無法確定具體是哪個類型,因此只能提示共有的屬性。而獨有方法經過聯合類型阻隔之后是無法進行語法提示。如果我們強行調用某個類型獨有的方法,可以看到編輯器會有錯誤提示。

有哪些關于TypeScript的知識點

如果確實需要使用獨有方法,該當如何?

這就需要類型保護了,確實,如果聯合類型只能調用共有方法,似乎看起來也用處不是很大,好在有類型保護。類型保護也有好多種,我們分別來介紹下。

1. 類型斷言

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

上面代碼中通過一個 as 關鍵字實現了類型斷言。因為按照邏輯,我們知道,如果有 fly 方法,那么 animal 一定是 Bird  類型,但是編輯器不知道,所以通過 as 告訴編輯器此時 animal 就是 Bird 類型,Dog 類型的確定也是同理。

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

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

3. 通過 typeof 來做類型保護

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

上面代碼中如何沒有 if 里面的邏輯,直接進行判斷,編輯器則會給錯,因為如果是數字和字符串相加,則可能存在錯誤,因此通過 typeof 來確定,當  first 和 second 都是數字的時候,進行相加。

4. 通過 instanceof 來類型保護

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  中,類不僅可以用來實例化對象,也可以用來定義變量類型,當一個對象被一個類定義以后,表明這個對象的值就是這個類的實例,關于類這一塊的寫法有疑問,可以查閱下 ES7  相關內容,這里不做過多講解。

從代碼中我們可以看出,通過 instanceof 來確定具有聯合類型的形參是否是類的類型,當然這里如果要用 instanceof  來判斷,我們的自定義類型定義只能用 class。如果是 interface 定義的類型,使用 instanceof 則會報錯。

枚舉類型

枚舉這個概念,我們在 JS 中就已經接觸的比較多了,關于概念也不就不做過多的講解,直接上一段代碼。

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 的枚舉類型默認會有賦值,而且寫法也很簡單。再看方式一和方式二對枚舉類型的使用,我們可以看出,TS  枚舉類型還支持正反調用。

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

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 枚舉類型支持自定義值,且后面的枚舉屬性沒有賦值的話,會在原來的基礎上遞增。

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

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

函數泛型

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

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

這是我們之前講過的聯合類型,兩個參數既可以是數字也可以字符串。

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

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

通過在函數中定義一個泛型 T(名字可以自定義,一般用 T),這樣的話,我們就可以約束 first,second  類型一致,當我們試圖調用的時候實參類型不一致的時候,那么編輯器就會報錯。

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

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

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

這是單一泛型,但實際場景中往往是多個泛型:

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

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

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 類中構造函數通過聯合類型來定義 data  的類型,這在復雜的業務場景中顯然是不可取的,因為如果我們也不確定類型,在傳參之前,那么只能寫更多的類型或者定義成 any  類型,這就顯得很不靈活,這時候我們想到了泛型,是否可以應用到類中呢?

答案是肯定的。

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])  //直觀的寫法,和上面等價 data.getItem(0)

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

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

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' ])

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

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

命名空間

講到這里,我們之前已經新建了很多的 demo 文件,不知道你有沒有發現這樣一個奇怪的現象。

demo.ts

let a = 123// dosomething

demo1.ts

let a = '123'

當我們在 demo1.ts 文件中再去定義 a 這個變量的時候,a 會標紅,告訴我們 a 已經被聲明了 number 類型,這是為什么呢?

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

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

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()   } }

代碼中,我定義了四個類,上面提到,如果我把 D 這個類通過 export 導出,這樣其他文件中就可以繼續使用 A  或者其他幾個類名了,但是我現在有個需求是這樣的,我不想把 A、B、C 三個類暴露出去,而且在外面能不能通過想通過對象的方式去調用 D 這個類。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 就只能調用到 D。如果還想調用其他類,只需要在前面去 export 這個類就好了。

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

命名空間本質上是一個對象,它的作用就是將一系列相關的全局變量變成一個對象的屬性,再看下上面的代碼編譯成 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;

從上面可以看出,通過一個立即執行函數并且傳了一個變量進去,然后把導出的方法掛載在變量上,這樣就可以在外面通過對象屬性的方式調用類。

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

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

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

// 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 中定制一個文件,補充你想要的類型 map 方法并實現函數掛載在 Observable 原型上,然后在 consumer.ts  就可以使用 Observable 類型里面的 map。

TypeScript 高級語法

類的裝飾器

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

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

當我們覺得完美的時候,編輯器給了我們一個標紅:

有哪些關于TypeScript的知識點

其實裝飾器是一個實驗性質的語法,所以不能直接使用,需要打開實驗支持,修改 tsconfig 的以下兩個選項:

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

修改完配置之后,就發現終端正確輸出了。

但是這里我還要再拋出一個問題,裝飾器的運行時機是什么時候呢,是在類實例化的時候嗎?

其實裝飾器在類創建的時候就已經運行裝飾器了,可以自行注釋掉實例化語句,再運行,看控制臺是否有 log。

類的裝飾器修飾函數接受的參數是類的構造函數,我們可以改一下 Decorator 來驗證一下:

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

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

@Decorator @Decorator1 class Demo{}

多個裝飾器執行順序為先下后上。

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

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

通過這樣,我們可以傳一些參數進去,然后函數內部去控制裝飾器的裝飾。

不知道你有沒有發現,我們在驗證裝飾器參數的時候,當我們通過類的實例去調用我們掛載在裝飾器原型的方法的時候,雖然沒有報錯,但是編輯器沒有給我們提示,這是很不符合我們預期的。上面那種裝飾器寫法很簡單,但很直觀。

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

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())

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

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

這個是一個泛型,T 繼承了一個構造函數也可以說是繼承了一個類,構造函數參數是一個展開運算符,表示接收多個參數。

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

方法裝飾器

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

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())

這就實現了給類的方法進行裝飾,當我們給類的普通方法進行裝飾的時候,裝飾器函數中接收的參數 target 對應的是類的 prototype,key  是裝飾的普通方法的名字。

注意,我上面說的是普通方法。和類的裝飾器一樣,方法裝飾器的執行時機同樣是當方法被定義的時候。

剛才我已經強調了普通方法,接下來我就要說靜態方法了。

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

靜態方法的裝飾器函數中,第一個參數 target 對應的是類的構造函數。

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

不知道你有沒有發現,這個函數接收三個參數,而且第三個參數還是 descriptor,有點像 Object.defineProperty 這個  API,當我們在函數中調用 descriptor 的時候,編輯器會給我們提示。

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

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

當你試圖這樣去修改它的時候,運行編譯后文件將會報錯:

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

這是運行結果:

有哪些關于TypeScript的知識點

訪問器裝飾器

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

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     }}

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

裝飾器業務場景使用

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

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

這段代碼不用運行,我們都能知道,會報錯,因為 userInfo 沒有 name 屬性。因此如果我們想要不報錯,就會寫成這樣:

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

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

那是因為運行雖然沒有問題,但是如果我們還有很多類似于這樣的方法,我們是否要重復處理錯誤呢?能否用到之前講的裝飾器來處理錯誤:

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     }}

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

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

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     }}

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

項目中應用 TypeScript

腳手架搭建一個 TypeScript

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

而當我們去使用 TypeScript 編寫一個項目的時候,還需要配置 TypeScript 的編譯配置文件 tsconfig 以及 tslint.json  文件。

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

查看它的官網,我們知道這是一個以 TypeScript 為基礎的開源腳手架工具,幫助我們快速開始一個 TypeScript 項目,使用方法如下:

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

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

有哪些關于TypeScript的知識點

├── package.json  // 項目配置文件 

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

TypeScript library starter  創建的項目確實集成了很多優秀的開源工具,包括打包、單元測試、格式化代碼等,有興趣的同學可以自行深入研究下。

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

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

  • npm start:觀察者模式運行 rollup 工具打包代碼。

  • npm test:運行 Jest 工具跑單元測試。

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

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

其他一些命令在我們日常開發中使用不是非常多,有需要的同學可以再自行去了解。

TypeScript 實戰

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

npm uninstall create-react-app

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

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

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

項目搭建完成之后,我們把文件整理下,刪除一些我們不用的文件,同時把相關引用也刪除,最終文件目錄如下:

有哪些關于TypeScript的知識點

當我們使用 TS 去寫 React 的時候, jsx 就變成了 tsx。在 APP.tsx 文件中:

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

通過 React.FC 給函數定義了一個 React.FC 的函數類型,這是 React 中定義的函數類型。

前端 UI 開發,現在市面上也有很多封裝好的框架,讓我們可以快速搭建一個頁面,這里我們選用 ant-design,這個框架也是使用 TypeScript  進行開發的,所以我們使用它進行開發的時候,會有很多類型可以供我們使用,因此使用它去鞏固我們剛學習的 TypeScript 知識點會有更多的好處。

首先讓我們來安裝下這個組件庫:

npm install antd --save

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

import 'antd/dist/antd.css'

接下來我們去寫個登錄頁面,首頁新建一個 login.css:

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

然后我們去 antd-design 官網,把登錄組件代碼復制到我們的 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 函數的 values 編輯器給我們報隱患提示,我們也無法確定 value 的類型,但是又不能填寫 any。因此,我們可以去找下  Form 中定義的類型。mac 用戶把鼠標放在 import 中的 From 標簽上( windows 用戶按住  cmd),進入到源代碼中去,然后一直去查找我們的方法的定義,首先我們進入到了:

有哪些關于TypeScript的知識點

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

有哪些關于TypeScript的知識點

同理我們也可以找到 onFinishFailed:

有哪些關于TypeScript的知識點

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

經過上面的測試之后,我們的項目基本上就算已經搭建好了,接下來就可以繼續充實相關的頁面了。

這里再把文件整理下,把不需要的刪除,src 目錄下新建一個 pages 的目錄,然后我們的頁面組件都放在這里,把 login  的代碼也在這個文件夾下新建一個文件存放,然后我們再修改下 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 編寫的文件,因此需要再安裝一個類型定義文件:

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

到此,關于“有哪些關于TypeScript的知識點”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

阿拉善左旗| 星子县| 慈溪市| 沂南县| 麻江县| 从江县| 老河口市| 双鸭山市| 平泉县| 宁南县| 石台县| 昌江| 垫江县| 包头市| 黎平县| 宁波市| 元阳县| 开原市| 万州区| 柯坪县| 满洲里市| 宜章县| 湖北省| 登封市| 博白县| 永州市| 东台市| 建瓯市| 泰兴市| 渭源县| 布拖县| 奉节县| 拜泉县| 庄浪县| 霞浦县| 锡林浩特市| 巴林右旗| 镇远县| 太仆寺旗| 康马县| 河间市|