TypeScript 終極初學者指南

大家好,我是若川。持續組織了8個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列

在過去的幾年里 TypeScript 變得越來越流行,現在許多工作都要求開發人員了解 TypeScript,各大廠的大型項目基本都要求使用 TypeScript 編寫。

如果你已經對 JavaScript 很熟了, TypeScript 基本上也能快速上手,下面是我整理的一些初學者必備的一些知識點,如果你已經是個 TS 高手了,可以期待我后續的文章了~

Typescript 簡介

4641491375007387da72e0015d16cd4b.png

據官方描述:TypeScriptJavaScript 的超集,這意味著它可以完成 JavaScript 所做的所有事情,而且額外附帶了一些能力。

JavaScript 本身是一種動態類型語言,這意味著變量可以改變類型。使用 TypeScript 的主要原因是就是為了給 JavaScript 添加靜態類型。靜態類型意味著變量的類型在程序中的任何時候都不能改變。它可以防止很多bug !

Typescript 值得學嗎?

下面是學習 Typescript 的幾個理由:

  • 研究表明,TypeScript 可以發現 15% 的常見 bug

  • TypeScript 可以讓代碼的可讀性更好,你可以更好的理解代碼是在做什么。

  • TypeScript 可以你申請到更多好工作。

  • 學習 TypeScript 可以使你對 JavaScript 有更好的理解和新的視角。

當然,使用 Typescript 也有一些缺點:

  • TypeScript 的編寫時間比 JavaScript 要長,因為你必須要指定類型,對于一些較小的獨立項目,可能不值使用。

  • TypeScript 需要編譯,項目越大消耗時間越長。

但是,相比于提前發現更多的 bug,花更長的時間也是值得的。

TypeScript 中的類型

原始類型

JavaScript 中,有 7 種原始類型:

  • string

  • number

  • bigint

  • boolean

  • undefined

  • null

  • symbol

原始類型都是不可變的,你可以為原始類型的變量重新分配一個新值,但不能像更改對象、數組和函數一樣更改它的值。可以看下面的例子:

let?name?=?'ConardLi';
name.toLowerCase();
console.log(name);?//?ConardLi?-?字符串的方法并沒有改變字符串本身let?arr?=?[1,?3,?5,?7];
arr.pop();
console.log(arr);?//?[1,?3,?5]?-?數組的方法改變了數組

回到 TypeScript ,我們可以在聲明一個變量之后設置我們想要添加的類型 :type (我們一般稱之為“類型注釋”或“類型簽名”):

let?id:?number?=?5;
let?firstname:?string?=?'ConardLi';
let?hasDog:?boolean?=?true;let?unit:?number;?//?聲明變量而不賦值
unit?=?5;

但是,如果變量有默認值的話,一般我們也不需要顯式聲明類型,TypeScript ?會自動推斷變量的類型(類型推斷):

let?id?=?5;?//?number?類型
let?firstname?=?'ConardLi';?//?string?類型
let?hasDog?=?true;?//?boolean?類型hasDog?=?'yes';?//?ERROR

我們還可以將變量設置為聯合類型(聯合類型是可以分配多個類型的變量):

let?age:?string?|?number;
age?=?17;
age?=?'17';

TypeScript 中的數組

TypeScript 中,你可以定義數組包含的數據類型:

let?ids:?number[]?=?[1,?2,?3,?4,?5];?//?只能包含?number
let?names:?string[]?=?['ConardLi',?'Tom',?'Jerry'];?//?只能包含?string
let?options:?boolean[]?=?[true,?false,?false];?只能包含?true?false
let?books:?object[]?=?[{?name:?'Tom',?animal:?'cat'?},{?name:?'Jerry',?animal:?'mouse'?},
];?//?只能包含對象
let?arr:?any[]?=?['hello',?1,?true];?//?啥都行,回到了?JSids.push(6);
ids.push('7');?//?ERROR:?Argument?of?type?'string'?is?not?assignable?to?parameter?of?type?'number'.

你也可以使用聯合類型來定義包含多種類型的數組:

let?person:?(string?|?number?|?boolean)[]?=?['ConardLi',?1,?true];
person[0]?=?100;
person[1]?=?{name:?'ConardLi'}?//?Error?-?person?array?can't?contain?objects

如果數組有默認值, TypeScript 同樣也會進行類型推斷:

let?person?=?['ConardLi',?1,?true];?//?和上面的例子一樣
person[0]?=?100;
person[1]?=?{?name:?'ConardLi'?};?//?Error?-?person?array?can't?contain?objects

TypeScript 中可以定義一種特殊類型的數組:元組(Tuple)。元組是具有固定大小和已知數據類型的數組,它比常規數組更嚴格。

let?person:?[string,?number,?boolean]?=?['ConardLi',?1,?true];
person[0]?=?17;?//?Error?-?Value?at?index?0?can?only?be?a?string

TypeScript 中的對象

TypeScript 中的對象必須擁有所有正確的屬性和值類型:

//?使用特定的對象類型注釋聲明一個名為?person?的變量
let?person:?{name:?string;age:?number;isProgrammer:?boolean;
};//?給?person?分配一個具有所有必要屬性和值類型的對象
person?=?{name:?'ConardLi',age:?17,isProgrammer:?true,
};person.age?=?'17';?//?ERROR:?should?be?a?numberperson?=?{name:?'Tom',age:?3,
};?
//?ERROR:?missing?the?isProgrammer?property

在定義對象的類型時,我們通常會使用 interface。如果我們需要檢查多個對象是否具有相同的特定屬性和值類型時,是很有用的:

interface?Person?{name:?string;age:?number;isProgrammer:?boolean;
}let?person1:?Person?=?{name:?'ConardLi',age:?17,isProgrammer:?true,
};let?person2:?Person?=?{name:?'Tom',age:?3,isProgrammer:?false,
};

我們還可以用函數的類型簽名聲明一個函數屬性,通用函數(sayHi)和箭頭函數(sayBye)都可以聲明:

interface?Animal?{eat(name:?string):?string;speak:?(name:?string)?=>?string;
}let?tom:?Animal?=?{eat:?function?(name:?string)?{return?`eat?${name}`;},speak:?(name:?string)?=>?`speak?${name}`,
};console.log(tom.eat('Jerry'));
console.log(tom.speak('哈哈哈'));

需要注意的是,雖然 eat、speak 分別是用普通函數和箭頭函數聲明的,但是它們具體是什么樣的函數類型都可以,Typescript 是不關心這些的。

TypeScript 中的函數

我們可以定義函數參數和返回值的類型:

//?定義一個名為?circle?的函數,它接受一個類型為?number?的直徑變量,并返回一個字符串
function?circle(diam:?number):?string?{return?'圓的周長為:'?+?Math.PI?*?diam;
}console.log(circle(10));?//?圓的周長為:31.41592653589793

ES6 箭頭函數的寫法:

const?circle?=?(diam:?number):?string?=>?{return?'圓的周長為:'?+?Math.PI?*?diam;
};

我們沒必要明確聲明 circle 是一個函數,TypeScript 會進行類型推斷。TypeScript 還會推斷函數的返回類型,但是如果函數體比較復雜,還是建議清晰的顯式聲明返回類型。

我們可以在參數后添加一個?,表示它為可選參數;另外參數的類型也可以是一個聯合類型:

const?add?=?(a:?number,?b:?number,?c?:?number?|?string)?=>?{console.log(c);return?a?+?b;
};console.log(add(5,?4,?'可以是?number、string,也可以為空'));

如果函數沒有返回值,在 TS 里表示為返回 void,你也不需要顯式聲明,TS 一樣可以進行類型推斷:

const?log?=?(msg:?string):?void?=>?{console.log('打印一些內容:?'?+?msg);
};

any 類型

使 any 類型,我們基本上可以將 TypeScript 恢復為 JavaScript

let?name:?any?=?'ConardLi';
name?=?17;
name?=?{?age:?17?};

如果代碼里使用了大量的 any,那 TypeScript 也就失去了意義,所以我們應該盡量避免使用 any

DOM 和類型轉換

TypeScript 沒辦法像 JavaScript 那樣訪問 DOM。這意味著每當我們嘗試訪問 DOM 元素時,TypeScript 都無法確定它們是否真的存在。

const?link?=?document.querySelector('a');console.log(link.href);?//?ERROR:?Object?is?possibly?'null'.?TypeScript?can't?be?sure?the?anchor?tag?exists,?as?it?can't?access?the?DOM

使用非空斷言運算符 (!),我們可以明確地告訴編譯器一個表達式的值不是 nullundefined。當編譯器無法準確地進行類型推斷時,這可能很有用:

//?我們明確告訴?TS?a?標簽肯定存在
const?link?=?document.querySelector('a')!;console.log(link.href);?//?conardli.top

這里我們沒必要聲明 link 變量的類型。這是因為 TypeScript 可以通過類型推斷確認它的類型為 HTMLAnchorElement

但是如果我們需要通過 classid 來選擇一個 DOM 元素呢?這時 TypeScript 就沒辦法推斷類型了:

const?form?=?document.getElementById('signup-form');console.log(form.method);
//?ERROR:?Object?is?possibly?'null'.
//?ERROR:?Property?'method'?does?not?exist?on?type?'HTMLElement'.

我們需要告訴 TypeScript form 確定是存在的,并且我們知道它的類型是 ?HTMLFormElement。我們可以通過類型轉換來做到這一點:

const?form?=?document.getElementById('signup-form')?as?HTMLFormElement;console.log(form.method);?//?post

TypeScript 還內置了一個 Event 對象。如果我們在表單中添加一個 submit 的事件偵聽器,TypeScript 可以自動幫我們推斷類型錯誤:

const?form?=?document.getElementById('signup-form')?as?HTMLFormElement;form.addEventListener('submit',?(e:?Event)?=>?{e.preventDefault();?//?阻止頁面刷新console.log(e.tarrget);?//?ERROR:?Property?'tarrget'?does?not?exist?on?type?'Event'.?Did?you?mean?'target'?
});

TypeScript 中的類

我們可以定義類中每條數據的類型:

class?Person?{name:?string;isCool:?boolean;age:?number;constructor(n:?string,?c:?boolean,?a:?number)?{this.name?=?n;this.isCool?=?c;this.age?=?a;}sayHello()?{return?`Hi,我是?${this.name}?,我今年?${this.age}?歲了`;}
}const?person1?=?new?Person('ConardLi',?true,?17);
const?person2?=?new?Person('Jerry',?'yes',?20);?//?ERROR:?Argument?of?type?'string'?is?not?assignable?to?parameter?of?type?'boolean'.console.log(person1.sayHello());?//?Hi,?我是?ConardLi,我今年?17?歲了

我們可以創建一個僅包含從 Person 構造的對象數組:

let?People:?Person[]?=?[person1,?person2];

我們可以給類的屬性添加訪問修飾符,TypeScript 還提供了一個新的 readonly 訪問修飾符。

class?Person?{readonly?name:?string;?//?不可以變的private?isCool:?boolean;?//?類的私有屬性、外部訪問不到protected?email:?string;?//?只能從這個類和子類中進行訪問和修改public?age:?number;?//?任何地方都可以訪問和修改constructor(n:?string,?c:?boolean,?a:?number)?{this.name?=?n;this.isCool?=?c;this.age?=?a;}sayHello()?{return?`Hi,我是?${this.name}?,我今年?${this.age}?歲了`;}
}const?person1?=?new?Person('ConardLi',?true,?'conard@xx.com',?17);
console.log(person1.name);?//?ConardLi
person1.name?=?'Jerry';?//?Error:?read?only

我們可以通過下面的寫法,屬性會在構造函數中自動分配,我們類會更加簡潔:

class?Person?{constructor(readonly?name:?string,private?isCool:?boolean,protected?email:?string,public?age:?number)?{}
}

如果我們省略訪問修飾符,默認情況下屬性都是 public,另外和 JavaScript 一樣,類也是可以 extends 的。

TypeScript 中的接口

接口定義了對象的外觀:

interface?Person?{name:?string;age:?number;
}function?sayHi(person:?Person)?{console.log(`Hi?${person.name}`);
}sayHi({name:?'ConardLi',age:?17,
});?//?Hi?ConardLi

你還可以使用類型別名定義對象類型:

type?Person?=?{name:?string;age:?number;
};

或者可以直接匿名定義對象類型:

function?sayHi(person:?{?name:?string;?age:?number?})?{console.log(`Hi?${person.name}`);
}

interfacetype 非常相似,很多情況下它倆可以隨便用。比如它們兩個都可以擴展:

擴展 interface

interface?Animal?{name:?string
}interface?Bear?extends?Animal?{honey:?boolean
}const?bear:?Bear?=?{name:?"Winnie",honey:?true,
}

擴展 type

type?Animal?=?{name:?string
}type?Bear?=?Animal?&?{honey:?boolean
}const?bear:?Bear?=?{name:?"Winnie",honey:?true,
}

但是有個比較明顯的區別,interface 是可以自動合并類型的,但是 type 不支持:

interface?Animal?{name:?string
}interface?Animal?{tail:?boolean
}const?dog:?Animal?=?{name:?"Tom",tail:?true,
}

類型別名在創建后無法更改:

type?Animal?=?{name:?string
}type?Animal?=?{tail:?boolean
}
//?ERROR:?Duplicate?identifier?'Animal'.

一般來說,當你不知道用啥的時候,默認就用 interface 就行,直到 interface 滿足不了我們的需求的時候再用 type

類的 interface

我們可以通過實現一個接口來告訴一個類它必須包含某些屬性和方法:

interface?HasFormatter?{format():?string;
}class?Person?implements?HasFormatter?{constructor(public?username:?string,?protected?password:?string)?{}format()?{return?this.username.toLocaleLowerCase();}
}let?person1:?HasFormatter;
let?person2:?HasFormatter;person1?=?new?Person('ConardLi',?'admin123');
person2?=?new?Person('Tom',?'admin123');console.log(person1.format());?//?conardli

確保 people 是一個實現 HasFormatter 的對象數組(確保每 people 都有 format 方法):

let?people:?HasFormatter[]?=?[];
people.push(person1);
people.push(person2);

泛型

泛型可以讓我們創建一個可以在多種類型上工作的組件,它能夠支持當前的數據類型,同時也能支持未來的數據類型,這大大提升了組件的可重用性。我們來看下面這個例子:

addID 函數接受一個任意對象,并返回一個新對象,其中包含傳入對象的所有屬性和值,以及一個 01000 之間隨機的 id 屬性。

const?addID?=?(obj:?object)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?};
};let?person1?=?addID({?name:?'John',?age:?40?});console.log(person1.id);?//?271
console.log(person1.name);?//?ERROR:?Property?'name'?does?not?exist?on?type?'{?id:?number;?}'.

當我們嘗試訪問 name 屬性時,TypeScript 會出錯。這是因為當我們將一個對象傳遞給 addID 時,我們并沒有指定這個對象應該有什么屬性 —— 所以 TypeScript 不知道這個對象有什么屬性。因此,TypeScript 知道的唯一屬性返回對象的 id

那么,我們怎么將任意對象傳遞給 addID,而且仍然可以告訴 TypeScript 該對象具有哪些屬性和值?這種場景就可以使用泛型了, <T>T 被稱為類型參數:

//?<T>?只是一種編寫習慣?-?我們也可以用?<X>?或?<A>
const?addID?=?<T>(obj:?T)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?};
};

這是啥意思呢?現在當我們再將一個對象傳遞給 addID 時,我們已經告訴 TypeScript 來捕獲它的類型了 —— 所以 T 就變成了我們傳入的任何類型。addID 現在會知道我們傳入的對象上有哪些屬性。

但是,現在有另一個問題:任何東西都可以傳入 addIDTypeScript 將捕獲類型而且并不會報告問題:

let?person1?=?addID({?name:?'ConardLi',?age:?17?});
let?person2?=?addID('Jerry');?//?傳遞字符串也沒問題console.log(person1.id);?//?188
console.log(person1.name);?//?ConardLiconsole.log(person2.id);
console.log(person2.name);?//?ERROR:?Property?'name'?does?not?exist?on?type?'"Jerry"?&?{?id:?number;?}'.

當我們傳入一個字符串時,TypeScript 沒有發現任何問題。只有我們嘗試訪問 name 屬性時才會報告錯誤。所以,我們需要一個約束:我們需要通過將泛型類型 T 作為 object 的擴展,來告訴 TypeScript 只能接受對象:

const?addID?=?<T?extends?object>(obj:?T)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?};
};let?person1?=?addID({?name:?'John',?age:?40?});
let?person2?=?addID('Jerry');?//?ERROR:?Argument?of?type?'string'?is?not?assignable?to?parameter?of?type?'object'.

錯誤馬上就被捕獲了,完美…… 好吧,也不完全是。在 JavaScript 中,數組也是對象,所以我們仍然可以通過傳入數組來逃避類型檢查:

let?person2?=?addID(['ConardLi',?17]);?//?傳遞數組沒問題console.log(person2.id);?//?188
console.log(person2.name);?//?Error:?Property?'name'?does?not?exist?on?type?'(string?|?number)[]?&?{?id:?number;?}'.

要解決這個問題,我們可以這樣說:object 參數應該有一個帶有字符串值的 name 屬性:

const?addID?=?<T?extends?{?name:?string?}>(obj:?T)?=>?{let?id?=?Math.floor(Math.random()?*?1000);return?{?...obj,?id?};
};let?person2?=?addID(['ConardLi',?17]);?//?ERROR:?argument?should?have?a?name?property?with?string?value

泛型允許在參數和返回類型提前未知的組件中具有類型安全。

TypeScript 中,泛型用于描述兩個值之間的對應關系。在上面的例子中,返回類型與輸入類型有關。我們用一個泛型來描述對應關系。

另一個例子:如果需要接受多個類型的函數,最好使用泛型而不是 any 。下面展示了使用 any 的問題:

function?logLength(a:?any)?{console.log(a.length);?//?No?errorreturn?a;
}let?hello?=?'Hello?world';
logLength(hello);?//?11let?howMany?=?8;
logLength(howMany);?//?undefined?(but?no?TypeScript?error?-?surely?we?want?TypeScript?to?tell?us?we've?tried?to?access?a?length?property?on?a?number!)

我們可以嘗試使用泛型:

function?logLength<T>(a:?T)?{console.log(a.length);?//?ERROR:?TypeScript?isn't?certain?that?`a`?is?a?value?with?a?length?propertyreturn?a;
}

好,至少我們現在得到了一些反饋,可以幫助我們持續改進我們的代碼。

解決方案:使用一個泛型來擴展一個接口,確保傳入的每個參數都有一個 length 屬性:

interface?hasLength?{length:?number;
}function?logLength<T?extends?hasLength>(a:?T)?{console.log(a.length);return?a;
}let?hello?=?'Hello?world';
logLength(hello);?//?11let?howMany?=?8;
logLength(howMany);?//?Error:?numbers?don't?have?length?properties

我們也可以編寫這樣一個函數,它的參數是一個元素數組,這些元素都有一個 length 屬性:

interface?hasLength?{length:?number;
}function?logLengths<T?extends?hasLength>(a:?T[])?{a.forEach((element)?=>?{console.log(element.length);});
}let?arr?=?['This?string?has?a?length?prop',['This',?'arr',?'has',?'length'],{?material:?'plastic',?length:?17?},
];logLengths(arr);
//?29
//?4
//?30

泛型是 TypeScript 的一個很棒的特性!

泛型接口

當我們不知道對象中的某個值是什么類型時,可以使用泛型來傳遞該類型:

//?The?type,?T,?will?be?passed?in
interface?Person<T>?{name:?string;age:?number;documents:?T;
}//?We?have?to?pass?in?the?type?of?`documents`?-?an?array?of?strings?in?this?case
const?person1:?Person<string[]>?=?{name:?'ConardLi',age:?17,documents:?['passport',?'bank?statement',?'visa'],
};//?Again,?we?implement?the?`Person`?interface,?and?pass?in?the?type?for?documents?-?in?this?case?a?string
const?person2:?Person<string>?=?{name:?'Tom',age:?20,documents:?'passport,?P45',
};

枚舉

枚舉是 TypeScriptJavaScript 帶來的一個特殊特性。枚舉允許我們定義或聲明一組相關值,可以是數字或字符串,作為一組命名常量。

enum?ResourceType?{BOOK,AUTHOR,FILM,DIRECTOR,PERSON,
}console.log(ResourceType.BOOK);?//?0
console.log(ResourceType.AUTHOR);?//?1//?從?1?開始
enum?ResourceType?{BOOK?=?1,AUTHOR,FILM,DIRECTOR,PERSON,
}console.log(ResourceType.BOOK);?//?1
console.log(ResourceType.AUTHOR);?//?2

默認情況下,枚舉是基于數字的 — 它們將字符串值存儲為數字。但它們也可以是字符串:

enum?Direction?{Up?=?'Up',Right?=?'Right',Down?=?'Down',Left?=?'Left',
}console.log(Direction.Right);?//?Right
console.log(Direction.Down);?//?Down

當我們有一組相關的常量時,枚舉就可以派上用場了。例如,與在代碼中使用非描述性數字不同,枚舉通過描述性常量使代碼更具可讀性。

枚舉還可以防止錯誤,因為當你輸入枚舉的名稱時,智能提示將彈出可能選擇的選項列表。

TypeScript 嚴格模式

建議在 tsconfig.json 中啟用所有嚴格的類型檢查操作文件。這可能會導致 TypeScript 報告更多的錯誤,但也更有助于幫你提前發現發現程序中更多的 bug

//?tsconfig.json"strict":?true

嚴格模式實際上就意味著:禁止隱式 any 和 嚴格的空檢查。

禁止隱式 any

在下面的函數中,TypeScript 已經推斷出參數 aany 類型的。當我們向該函數傳遞一個數字,并嘗試打印一個 name 屬性時,沒有報錯:

function?logName(a)?{//?No?error??console.log(a.name);
}logName(97);

打開 noImplicitAny 選項后,如果我們沒有顯式地聲明 a 的類型,TypeScript 將立即標記一個錯誤:

//?ERROR:?Parameter?'a'?implicitly?has?an?'any'?type.
function?logName(a)?{console.log(a.name);
}

嚴格的空檢查

strictNullChecks 選項為 false 時,TypeScript 實際上會忽略 nullundefined。這可能會在運行時導致意外錯誤。

strictNullChecks 設置為 true 時,nullundefined 有它們自己的類型,如果你將它們分配給一個期望具體值(例如,字符串)的變量,則會得到一個類型錯誤。

let?whoSangThis:?string?=?getSong();const?singles?=?[{?song:?'touch?of?grey',?artist:?'grateful?dead'?},{?song:?'paint?it?black',?artist:?'rolling?stones'?},
];const?single?=?singles.find((s)?=>?s.song?===?whoSangThis);console.log(single.artist);

singles.find 并不能保證它一定能找到這首歌 — 但是我們已經編寫了下面的代碼,好像它肯定能找到一樣。

通過將 strictNullChecks 設置為 trueTypeScript 將拋出一個錯誤,因為在嘗試使用它之前,我們沒有保證 single 一定存在:

const?getSong?=?()?=>?{return?'song';
};let?whoSangThis:?string?=?getSong();const?singles?=?[{?song:?'touch?of?grey',?artist:?'grateful?dead'?},{?song:?'paint?it?black',?artist:?'rolling?stones'?},
];const?single?=?singles.find((s)?=>?s.song?===?whoSangThis);console.log(single.artist);?//?ERROR:?Object?is?possibly?'undefined'.

TypeScript 基本上是告訴我們在使用 single 之前要確保它存在。我們需要先檢查它是否為 nullundefined

if?(single)?{console.log(single.artist);?//?rolling?stones
}

TypeScript 中的類型收窄

TypeScript 中,變量可以從不太精確的類型轉移到更精確的類型,這個過程稱為類型收窄。

下面是一個簡單的例子,展示了當我們使用帶有 typeofif 語句時,TypeScript 如何將不太特定的 string | number 縮小到更特定的類型:

function?addAnother(val:?string?|?number)?{if?(typeof?val?===?'string')?{//?ts?將?val?視為一個字符串return?val.concat('?'?+?val);}//?ts?知道?val?在這里是一個數字return?val?+?val;
}console.log(addAnother('哈哈'));?//?哈哈?哈哈
console.log(addAnother(17));?//?34

另一個例子:下面,我們定義了一個名為 allVehicles 的聯合類型,它可以是 PlaneTrain 類型。

interface?Vehicle?{topSpeed:?number;
}interface?Train?extends?Vehicle?{carriages:?number;
}interface?Plane?extends?Vehicle?{wingSpan:?number;
}type?PlaneOrTrain?=?Plane?|?Train;function?getSpeedRatio(v:?PlaneOrTrain)?{console.log(v.carriages);?//?ERROR:?'carriages'?doesn't?exist?on?type?'Plane'
}

由于 getSpeedRatio 函數處理了多種類型,我們需要一種方法來區分 vPlane 還是 Train 。我們可以通過給這兩種類型一個共同的區別屬性來做到這一點,它帶有一個字符串值:

interface?Train?extends?Vehicle?{type:?'Train';carriages:?number;
}interface?Plane?extends?Vehicle?{type:?'Plane';wingSpan:?number;
}type?PlaneOrTrain?=?Plane?|?Train;

現在,TypeScript 可以縮小 v 的類型:

function?getSpeedRatio(v:?PlaneOrTrain)?{if?(v.type?===?'Train')?{return?v.topSpeed?/?v.carriages;}//?如果不是 Train,ts 知道它就是 Plane 了,聰明!return?v.topSpeed?/?v.wingSpan;
}let?bigTrain:?Train?=?{type:?'Train',topSpeed:?100,carriages:?20,
};console.log(getSpeedRatio(bigTrain));?//?5

另外,我們還可以通過實現一個類型保護來解決這個問題,可以看看這篇文章:什么是鴨子🦆類型?

TypeScript & React

TypeScript 完全支持 React 和 JSX。這意味著我們可以將 TypeScript 與三個最常見的 React 框架一起使用:

  • create-react-app (https://create-react-app.dev/docs/adding-typescript/)

  • Gatsby (https://www.gatsbyjs.com/docs/how-to/custom-configuration/typescript/)

  • Next.js (https://nextjs.org/learn/excel/typescript)

如果你需要一個更自定義的 React-TypeScript 配置,你可以字節配置 Webpacktsconfig.json。但是大多數情況下,一個框架就可以完成這項工作。

例如,要用 TypeScript 設置 create-react-app,只需運行:

npx?create-react-app?my-app?--template?typescript#?oryarn?create?react-app?my-app?--template?typescript

src 文件夾中,我們現在可以創建帶有 .ts (普通 TypeScript 文件)或 .tsx (帶有 ReactTypeScript 文件)擴展名的文件,并使用 TypeScript 編寫我們的組件。然后將其編譯成 public 文件夾中的 JavaScript

React props & TypeScript

Person 是一個 React 組件,它接受一個 props 對象,其中 name 應該是一個字符串,age 是一個數字。

//?src/components/Person.tsx
import?React?from?'react';const?Person:?React.FC<{name:?string;age:?number;
}>?=?({?name,?age?})?=>?{return?(<div><div>{name}</div><div>{age}</div></div>);
};export?default?Person;

一般我們更喜歡用 interface 定義 props

interface?Props?{name:?string;age:?number;
}const?Person:?React.FC<Props>?=?({?name,?age?})?=>?{return?(<div><div>{name}</div><div>{age}</div></div>);
};

然后我們嘗試將組件導入到 App.tsx,如果我們沒有提供必要的 propsTypeScript 會報錯。

import?React?from?'react';
import?Person?from?'./components/Person';const?App:?React.FC?=?()?=>?{return?(<div><Person?name='ConardLi'?age={17}?/></div>);
};export?default?App;

React hooks & TypeScript

useState()

我們可以用尖括號來聲明狀態變量的類型。如果我們省略了尖括號,TypeScript 會默認推斷 cash 是一個數字。因此,如果想讓它也為空,我們必須指定:

const?Person:?React.FC<Props>?=?({?name,?age?})?=>?{const?[cash,?setCash]?=?useState<number?|?null>(1);setCash(null);return?(<div><div>{name}</div><div>{age}</div></div>);
};

useRef()

useRef 返回一個可變對象,該對象在組件的生命周期內都是持久的。我們可以告訴 TypeScript ? ref 對象應該指向什么:

const?Person:?React.FC?=?()?=>?{//?Initialise?.current?property?to?nullconst?inputRef?=?useRef<HTMLInputElement>(null);return?(<div><input?type='text'?ref={inputRef}?/></div>);
};

參考

  • https://www.typescriptlang.org/docs/

  • https://react-typescript-cheatsheet.netlify.app/

  • https://www.freecodecamp.org/news/learn-typescript-beginners-guide

好了,這篇文章我們學習了一些 Typescript 的必備基礎,有了這些知識你已經可以應付大部分 TS 的應用場景了,后續我會出一些 TS 的高級技巧相關的文章,敬請期待吧 ~

98fb42cea2ec850b06a8474bdc8a4d06.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

4ddbb0dc8c8b6359b8b765d8c8589ab6.png

掃碼加我微信 ruochuan02、拉你進源碼共讀

今日話題

目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274761.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274761.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274761.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

繼承與多態(六)

繼承 1.繼承 a。.直接在類的后面加上冒號“&#xff1a;”后面跟基類&#xff0c;就該類就繼承了基類的一切特性了。 b。private類不能被繼承&#xff0c;只有public、protected類能被繼承。 c。private類不里面所有的屬性和方法都不能被外界訪問&#xff0c;只有他自己可以。 …

標記偏見_如何(巧妙地)扭曲視覺效果以支持您的偏見敘事

標記偏見Data is important — it is the logical justification for world-changing decisions. Unfortunately, arrays of numbers don’t tell as interpretable a story as a picture does, providing an insatiable need for data visualizations.數據很重要-這是改變世界…

高瓴投資,頂配創業團隊,dora 誠招前端 / Flutter

dora 是一個可以跨越設計稿&#xff0c;直接生成應用的新一代設計工具。讓任何 Creator 都能輕松構建個性化的網站和應用&#xff0c;無需編寫一行代碼。通過自主研發的全新技術&#xff0c;我們為用戶打造了完全自由度的設計與開發體驗&#xff0c;足以滿足任何復雜場景的個性…

獵鷹spacex_SpaceX:簡單,美觀的界面是未來

獵鷹spacex重點 (Top highlight)A photo has been floating around the internet showing how the interior of the new Dragon spacecraft differs from the Space Shuttle. The difference is staggering, but not entirely suprprising. After all the Shuttle started oper…

object的classid收集

比如&#xff1a; wbbrowser控件 <OBJECT idWB classidCLSID:8856F961-340A-11D0-A96B-00C04FD705A2 VIEWASTEXT></OBJECT> 畫圖控件 <OBJECT idSGrfxCtl1 classidclsid:369303C2-D7AC-11D0-89D5-00A0C90833E6 ></OBJECT> 上下滾動條控件 <OB…

如何高效學習前端新知識,拓展視野,我推薦

技術日新月異&#xff0c;發展迅速&#xff0c;作為一個與時俱進的互聯網人&#xff0c;需要不斷地學習擴寬視野。今天為大家推薦幾個技術領域中出類拔萃的公眾號&#xff0c;它們的每一篇推文都值得你點開&#xff01;1前端開發愛好者學習路線 數據結構算法 前端進階「前端開發…

開發交接文檔_為開發人員創造更好的設計交接體驗

開發交接文檔It’s 2020. We’re supposed to have flying cars and space travel. We should at least have our process for design handoff nailed down at this point.現在是2020年。我們應該有飛行汽車和太空旅行。 在這一點上&#xff0c;我們至少應該確定我們的設計移交…

同步器之Exchanger

類java.util.concurrent.Exchanger提供了一個同步點&#xff0c;在這個同步點&#xff0c;一對線程可以交換數據。每個線程通過exchange()方法的入口提供數據給他的伙伴線程&#xff0c;并接收他的伙伴線程提供的數據&#xff0c;并返回。 當在運行不對稱的活動時很有用&#x…

?Cookie 從入門到進階:一文徹底弄懂其原理以及應用

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列Cooki…

ui設計師常用的設計工具_2020年應該使用哪個UI設計工具?

ui設計師常用的設計工具重點 (Top highlight)It’s 2020, the market today is saturated with UI design tools. Ever since Sketch app came out with its sleek, simple, and efficient tool to craft user interface design, many companies have followed suit to take a …

Ajax拖放頁面元素(圖片)

最近了解了一點YUI的控件知識.先做個Ajax拖放頁面元素(圖片)以便學習參考. 現在有一些網站如QQ空間,都允許用戶自定義模塊,可以任意拖動模塊到各個地方去.YUI在這一方面做得比較好.下面以一組圖片的方式來說明如何運用Ajax拖放頁面元素: 第一步:在<head></head>標簽…

你不知道的vscode之空間控制

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列王志遠…

正則表達式說明

參考地址&#xff1a; 正則表達式說明 正則表達式全部符號解釋轉載于:https://www.cnblogs.com/s-bridge/archive/2012/06/26/2564396.html

lynda ux_UX心態

lynda uxI have had the pleasure of training and mentoring several UX people at the beginning of their careers.在職業生涯的初期&#xff0c;我很高興接受培訓和指導。 Whatever your background or experience, I’ve found repeatedly that there are some key miles…

什么 Leader 值得追隨?

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。目前建有江西|湖南|湖北 籍 前端群&#xff0c;想進群的可以加我微信 ruochuan12 進群。歷…

pico8 掌機_使用Pico-8構建自己的復古游戲

pico8 掌機An example of the kinds of pixel animations people make in Pico-8.人們在Pico-8中制作的各種像素動畫的示例。 Are you a fan of old school video games? What if I told you there’s an NES-style game devkit with the sound/sprite/code tools all built i…

C#中Brush、Color、String相互轉換

1、String轉換成Color Color color (Color)ColorConverter.ConvertFromString(string); 2、String轉換成Brush BrushConverter brushConverter new BrushConverter(); Brush brush (Brush)brushConverter.ConvertFromString(string); 3、Color轉換成Brush Brush …

實用 JavaScript 調試技巧

大家好&#xff0c;我是若川。持續組織了8個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外…

數據挖掘 點擊更多 界面_6(更多)技巧,可快速改善用戶界面

數據挖掘 點擊更多 界面重點 (Top highlight)Creating beautiful, usable, and efficient UIs takes time, with many design revisions along the way.創建漂亮&#xff0c;可用和高效的UI需要花費時間&#xff0c;并且在此過程中進行了許多設計修訂。 Making those constant…

簡單的ASP.NET無刷新分頁

1、新建一個分頁存儲過程&#xff1a; CREATE procedure [dbo].[P_Pager] (PageNumber int, PageSize int) as declare sql nvarchar(4000) set sql select top Convert(varchar, PageSize) * from T_Test where [type]1 and id not in (select top Convert(…