大家好,我是若川。持續組織了8個月源碼共讀活動,感興趣的可以點此加我微信 ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列
在過去的幾年里 TypeScript
變得越來越流行,現在許多工作都要求開發人員了解 TypeScript
,各大廠的大型項目基本都要求使用 TypeScript
編寫。
如果你已經對 JavaScript
很熟了, TypeScript
基本上也能快速上手,下面是我整理的一些初學者必備的一些知識點,如果你已經是個 TS
高手了,可以期待我后續的文章了~
Typescript 簡介

據官方描述:TypeScript
是 JavaScript
的超集,這意味著它可以完成 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
使用非空斷言運算符 (!
),我們可以明確地告訴編譯器一個表達式的值不是 null
或 undefined
。當編譯器無法準確地進行類型推斷時,這可能很有用:
//?我們明確告訴?TS?a?標簽肯定存在
const?link?=?document.querySelector('a')!;console.log(link.href);?//?conardli.top
這里我們沒必要聲明 link
變量的類型。這是因為 TypeScript
可以通過類型推斷確認它的類型為 HTMLAnchorElement
。
但是如果我們需要通過 class
或 id
來選擇一個 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}`);
}
interface
和 type
非常相似,很多情況下它倆可以隨便用。比如它們兩個都可以擴展:
擴展 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
函數接受一個任意對象,并返回一個新對象,其中包含傳入對象的所有屬性和值,以及一個 0
到 1000
之間隨機的 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
現在會知道我們傳入的對象上有哪些屬性。
但是,現在有另一個問題:任何東西都可以傳入 addID
,TypeScript
將捕獲類型而且并不會報告問題:
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',
};
枚舉
枚舉是 TypeScript
給 JavaScript
帶來的一個特殊特性。枚舉允許我們定義或聲明一組相關值,可以是數字或字符串,作為一組命名常量。
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
已經推斷出參數 a
是 any
類型的。當我們向該函數傳遞一個數字,并嘗試打印一個 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
實際上會忽略 null
和 undefined
。這可能會在運行時導致意外錯誤。
當 strictNullChecks
設置為 true
時,null
和 undefined
有它們自己的類型,如果你將它們分配給一個期望具體值(例如,字符串)的變量,則會得到一個類型錯誤。
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
設置為 true
, TypeScript
將拋出一個錯誤,因為在嘗試使用它之前,我們沒有保證 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
之前要確保它存在。我們需要先檢查它是否為 null
或 undefined
:
if?(single)?{console.log(single.artist);?//?rolling?stones
}
TypeScript 中的類型收窄
在 TypeScript
中,變量可以從不太精確的類型轉移到更精確的類型,這個過程稱為類型收窄。
下面是一個簡單的例子,展示了當我們使用帶有 typeof
的 if
語句時,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
的聯合類型,它可以是 Plane
或 Train
類型。
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
函數處理了多種類型,我們需要一種方法來區分 v
是 Plane
還是 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
配置,你可以字節配置 Webpack
和 tsconfig.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
(帶有 React
的 TypeScript
文件)擴展名的文件,并使用 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
,如果我們沒有提供必要的 props
,TypeScript
會報錯。
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 的高級技巧相關的文章,敬請期待吧 ~
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助3000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。
掃碼加我微信 ruochuan02、拉你進源碼共讀群
今日話題
目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 ruochuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~