Typescript真香秘笈
本文由?IMWeb?首發(fā)于 IMWeb 社區(qū)網(wǎng)站 imweb.io。點(diǎn)擊閱讀原文查看 IMWeb 社區(qū)更多精彩文章。
1. 前言
2018年Stack Overflow Developer的調(diào)研(https://insights.stackoverflow.com/survey/2018/)顯示,TypeScript已經(jīng)成為比JavaScript更受開發(fā)者喜愛的編程語言了。

之前我其實(shí)對(duì)于typescript沒有太多好感,主要是認(rèn)為其學(xué)習(xí)成本比較高,寫起代碼來還要多寫很多類型聲明,并且會(huì)受到靜態(tài)類型檢查的限制,很不自由,與javascript的設(shè)計(jì)哲學(xué)♂相悖。我相信有很多人也抱持著這樣的想法。
然而,最近由于項(xiàng)目需要,學(xué)習(xí)并使用了一波typescript,結(jié)果。。。

2. Typescript是什么?
typescript,顧名思義,就是type + javascript,也就是加上了類型檢查的js。官方對(duì)于typescript的介紹也指出,typescript是javascript的超集。純粹的js語法,在typescript中是完全兼容的。但是反過來,用typescript語法編寫的代碼,卻不能在瀏覽器或者Node環(huán)境下直接運(yùn)行,因?yàn)閠ypescript本身并不是Ecmascript標(biāo)準(zhǔn)語法。
3. 為什么要使用Typescript?
很多人堅(jiān)持javascript而不愿使用typescript的一個(gè)很大原因是認(rèn)為javascript的動(dòng)態(tài)性高,基本不需要考慮類型,而使用typescript將會(huì)大大削弱編碼的自由度。但實(shí)際上,動(dòng)態(tài)性并不總是那么美好的。至少,現(xiàn)在javascript的動(dòng)態(tài)性帶來了以下三方面的問題:
代碼可讀性差,維護(hù)成本高。
所謂”動(dòng)態(tài)一時(shí)爽,重構(gòu)火葬場(chǎng)“。缺乏類型聲明,對(duì)于自己非常熟悉的代碼而言,問題不大。但是如果對(duì)于新接手或者太長時(shí)間沒有接觸的代碼,理解代碼的時(shí)候需要自行腦補(bǔ)各種字段與類型,如果不幸項(xiàng)目規(guī)模比較龐大,也沒什么注釋,那么你的反應(yīng)大概會(huì)是像這樣的:

有了typescript,每個(gè)變量類型與結(jié)構(gòu)一目了然,根本無需自行腦補(bǔ)。搭配編輯器的智能提示,體驗(yàn)可謂舒適,媽媽再也不用擔(dān)心我拼錯(cuò)字段名了。
缺乏類型檢查,低級(jí)錯(cuò)誤出現(xiàn)幾率高。
人的專注力很難一直都保持高度在線狀態(tài),如果沒有類型檢查,很容易出現(xiàn)一些低級(jí)錯(cuò)誤。例如給某個(gè)string變量賦值數(shù)值,或給對(duì)象賦值時(shí)候缺少了某些必要字段,調(diào)用函數(shù)時(shí)漏傳或者錯(cuò)傳參數(shù)等。這些看起來很低級(jí)的錯(cuò)誤,雖然大多數(shù)情況下在自測(cè)或者測(cè)試階段,都能被驗(yàn)出來,但是總會(huì)浪費(fèi)你的一些時(shí)間去debug。
使用typescript,這種情況甚至不會(huì)發(fā)生,一旦你粗心地賦錯(cuò)值,編輯器立即標(biāo)紅提示,將bug扼殺在搖籃之中。
類型不確定,運(yùn)行時(shí)解析器需要進(jìn)行類型推斷,存在性能問題。
我們知道javascript是邊解析邊執(zhí)行的,由于類型不確定,所以同一句代碼可能需要被多次編譯,這就造成性能上的開銷。
雖然typescript現(xiàn)在無法直接解決性能上的問題,因?yàn)閠ypescript最終是編譯成javascript代碼的,但是現(xiàn)在已經(jīng)有從typescript編譯到WebAssembly的工具了:https://github.com/AssemblyScript/assemblyscript。
好了,如果看完了上面的內(nèi)容,您還是表示對(duì)于typescript不感興趣,那么后面的內(nèi)容就可以忽略了哈哈哈。。。

4. Typescript基礎(chǔ)篇
4.1 基礎(chǔ)類型
typescript中的基礎(chǔ)類型有:

其中,number、string、boolean、object、null、undefined、symbol都是比較簡單的。
例如:
let num: number = 1; // 聲明一個(gè)number類型的變量
let str: string = 'string'; // 聲明一個(gè)string類型的變量
let bool: boolean = true; // 聲明一個(gè)boolean類型的變量
let obj: object = { // 聲明一個(gè)object類型的變量
a: 1,
}
let syb: symbol = Symbol(); // 聲明一個(gè)symbol類型的變量
null和undefined可以賦值給除了never的其他類型。
如果給變量賦予與其聲明類型不兼容的值,就會(huì)有報(bào)錯(cuò)提示。
例如:

Array 數(shù)組類型
在typescript中,有兩種聲明數(shù)組類型的方式。
方式一:
let arr: Array<number> = [1, 2, 3]; // 聲明一個(gè)數(shù)組類型的變量
方式二:
let arr: number[] = [1, 2, 3]; // 聲明一個(gè)數(shù)組類型的變量
Tuple 元組類型
元組類似于數(shù)組,只不過元組元素的個(gè)數(shù)和類型都是確定的。
let tuple: [number, boolean] = [0, false];
any類型
當(dāng)不知道變量的類型時(shí),可以先將其設(shè)置為any類型。
設(shè)置為any類型后,相當(dāng)于告訴typescript編譯器跳過這個(gè)變量的檢查,因此可以訪問、設(shè)置這個(gè)變量的任何屬性,或者給這個(gè)變量賦任何值,編譯器都不會(huì)報(bào)錯(cuò)。
let foo: any;
foo.test();
foo = 1;
foo = 'a';
void類型
通常用來聲明沒有返回值的函數(shù)的返回值類型。
function foo(): void {
}
never類型
通常用來聲明永遠(yuǎn)不會(huì)正常返回的函數(shù)的返回值類型:
// 返回never的函數(shù)必須存在無法達(dá)到的終點(diǎn)
function error(message: string): never {
throw new Error(message);
}
// 返回never的函數(shù)必須存在無法達(dá)到的終點(diǎn)
function infiniteLoop(): never {
while (true) {
}
}
never與void的區(qū)別便是,void表明函數(shù)會(huì)正常返回,但是返回值為空。never表示的是函數(shù)永遠(yuǎn)不會(huì)正常返回,所以不可能有值。
enum 枚舉類型
使用枚舉類型可以為一組數(shù)值賦予友好的名字。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
默認(rèn)情況下,從0開始為元素編號(hào)。你也可以手動(dòng)的指定成員的數(shù)值。例如,我們將上面的例子改成從?1開始編號(hào):
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
或者,全部都采用手動(dòng)賦值:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
元素類型也支持字符串類型:
enum Color {Red = 'Red', Green = 'Green', Blue = 'Blue'}
let c: Color = Color.Green;
枚舉類型提供的一個(gè)便利是你可以由枚舉的值得到它的名字。例如,我們知道數(shù)值為2,但是不確定它映射到Color里的哪個(gè)名字,我們可以查找相應(yīng)的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // 顯示'Green'因?yàn)樯厦娲a里它的值是2
4.2 類型斷言
有點(diǎn)類似其他強(qiáng)類型語言的強(qiáng)制類型轉(zhuǎn)換,可以將一個(gè)值斷言成某種類型,編譯器不會(huì)進(jìn)行特殊的數(shù)據(jù)檢查和結(jié)構(gòu),所以需要自己確保斷言的準(zhǔn)確性。
斷言有兩種形式,一種為尖括號(hào)語法,一種為as語法。
尖括號(hào)語法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
as語法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
在大部分情況下,這兩種語法都可以使用,但是在jsx中就只能使用as語法了。
5. Typescript進(jìn)階篇
5.1 函數(shù)
函數(shù)類型:
函數(shù)類型主要聲明的是參數(shù)和返回值的類型。
function sum(a: number, b: number): number {
return a + b;
}
約等于
const sum: (numberA: number, numberB: number) => number = function(a: number, b: number): number {
return a + b;
}
注意到類型定義時(shí)參數(shù)的名稱不一定要與實(shí)際函數(shù)的名稱一致,只要類型兼容即可。
可選參數(shù):
函數(shù)參數(shù)默認(rèn)都是必填的,我們也可以使用可選參數(shù)。
function sum(a: number, b: number, c?: number): number {
return c ? a + b + c : a + b;
}
重載:
javascript本身是個(gè)動(dòng)態(tài)語言。javascript里函數(shù)根據(jù)傳入不同的參數(shù)而返回不同類型的數(shù)據(jù)是很常見的。
來看個(gè)簡單但沒什么用的例子:
function doNothing(input: number): number;
function doNothing(input: string): string;
function doNothing(input): any {
return input;
}
console.log(doNothing(123));
console.log(doNothing('123'));
當(dāng)然也可以使用聯(lián)合類型,但是編譯器就無法準(zhǔn)確知道返回值的具體類型。
function doNothing(input: number | string): number | string {
return input;
}
console.log(doNothing('123').length); // 錯(cuò)誤:Property 'length' does not exist on type 'string | number'
如果只是單純參數(shù)的個(gè)數(shù)不同,返回值類型一樣,建議使用可選參數(shù)而不是重載。
function sum(a: number, b: number, c?: number) {
return c ? a + b + c : a + b;
}
5.2 interface 接口
對(duì)于一些復(fù)雜的對(duì)象,需要通過接口來定義其類型。
interface SquareConfig {
color: string;
width: number;
}
const square: SquareConfig = {
color: 'red', width: 0,
};
可選屬性:
默認(rèn)情況下,每個(gè)屬性都是不能為空的。如果這么寫,將會(huì)有報(bào)錯(cuò)。
interface SquareConfig {
color: string;
width: number;
}
const square: SquareConfig = {
color: 'red',
};// error
可以將用"?"將width標(biāo)志位可選的屬性:
interface SquareConfig {
color: string;
width?: number;
}
const square: SquareConfig = {
color: 'red',
};
只讀屬性:
一些對(duì)象屬性只能在對(duì)象剛剛創(chuàng)建的時(shí)候修改其值。你可以在屬性名前用?readonly來指定只讀屬性。
interface Point {
readonly x: number;
readonly y: number;
}
如果在初始化后試圖修改只讀屬性的值,將會(huì)報(bào)錯(cuò)。
let p: Point = { x: 10, y: 20 };
p.x = 20; // error
函數(shù)類型:
接口除了能夠描述對(duì)象的結(jié)構(gòu)之外,還能描述函數(shù)的類型。
interface SumFunc {
(a: number, b: number): number;
}
let sum: SumFunc;
sum = (numberA: number, numberB: number) => {
return numberA + numberB;
}
可以看到函數(shù)的類型與函數(shù)定義時(shí)只要參數(shù)類型一致即可,參數(shù)名不一定要一樣。
可索引類型:
可索引類型,實(shí)際就是聲明對(duì)象的索引的類型,與對(duì)應(yīng)值的類型。接口支持兩種索引類型,一種是number,一種是string,通過可索引類型可以聲明一個(gè)數(shù)組類型。
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
5.3 class 類
typescript中的類是javascript中類的超集,所以如果你了解es6中的class的語法,也不難理解typescript中class的語法了。
這里主要說下typescript的class和javascript的class的不同之處:
只讀屬性
public、private、protected修飾符
抽象類
實(shí)現(xiàn)接口
只讀屬性
類似于接口中的只讀屬性,只能在類實(shí)例初始化的時(shí)候賦值。
class User {
readonly name: string;
constructor (theName: string) {
this.name = theName;
}
}
let user = new User('Handsome');
user.name = 'Handsomechan'; // 錯(cuò)誤!name是只讀的
public、private、protected修飾符:
public修飾符表示屬性是公開的,可以通過實(shí)例去訪問該屬性。類屬性默認(rèn)都是public屬性。
class Animal {
constructor(public name: string) {}
}
const animal = new Animal('tom');
console.log(animal.name); // 'tom'
注意在類的構(gòu)造函數(shù)參數(shù)前加上修飾符是一個(gè)語法糖,上面的寫法等價(jià)于:
class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
}
private修飾符表示屬性是私有的,只有實(shí)例的方法才能訪問該屬性。
class Animal {
getName(): string { return this.name }
constructor(private name: string) {}
}
const animal = new Animal('tom');
console.log(animal.getName()); // 'tom'
console.log(animal.name); // Property 'name' is private and only accessible within class 'Animal'.
protected修飾符表示屬性是保護(hù)屬性,只有實(shí)例的方法和派生類中的實(shí)例方法才能訪問到。
class Animal {
constructor(public name: string, protected age: number) {}
}
class Cat extends Animal {
getAge = ():number => {
return this.age;
}
}
const cat = new Cat('tom', 1);
console.log(cat.getAge()); // 1
抽象類:
抽象類做為其它派生類的基類使用。它們一般不會(huì)直接被實(shí)例化。不同于接口,抽象類可以包含成員的實(shí)現(xiàn)細(xì)節(jié)。?abstract關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法。
abstract class Animal {
abstract makeSound(): void; // 抽象方法,必須在派生類中實(shí)現(xiàn)
move(): void {
console.log('roaming the earch...');
}
}
class Sheep extends Animal {
makeSound() {
console.log('mie~');
}
}
const animal = new Animal(); // 錯(cuò)誤,抽象類不能直接實(shí)例化
const sheep = new Sheep();
sheep.makeSound();
sheep.move();
實(shí)現(xiàn)接口:
類可以實(shí)現(xiàn)一個(gè)接口,從而使得類滿足這個(gè)接口的約束條件。
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
5.4 泛型
泛型在強(qiáng)類型語言中很常見,泛型支持在編寫代碼時(shí)候使用類型參數(shù),而不必在一開始確定某種特定的類型。這樣做的原因有兩個(gè):
有時(shí)候沒辦法在代碼被使用之前知道類型。
例如我們封裝了一個(gè)request函數(shù),用來發(fā)起http請(qǐng)求,返回請(qǐng)求響應(yīng)字段。
我們?cè)趯?shí)現(xiàn)request函數(shù)的時(shí)候,實(shí)際上是不能知道響應(yīng)字段有哪些內(nèi)容的,因?yàn)檫@跟特定的請(qǐng)求相關(guān)。
所以我們將類型確定的任務(wù)留給了調(diào)用者。
// 簡單封裝了一個(gè)request函數(shù)
async function request<T>(url: string): Promise<T> {
try {
const result = await fetch(url).then((response) => {
return response.json();
});
return result;
} catch (e) {
console.log('request fail:', e);
throw e;
}
}
async function getUserInfo(userId: string): void {
const userInfo = await request<{
nickName: string;
age: number;
}>(`user_info?id=${userId}`)
console.log(userInfo); // { nickName: 'xx', age: xx }
}
getUserInfo('123');
提高代碼的復(fù)用率。
如果對(duì)于不同類型,代碼的操作都是一樣的,那么可以使用泛型來提高代碼的復(fù)用率。
// 獲取數(shù)組或者字符串的長度
function getLen<T extends Array<any> | string>(arg: T): number {
return arg ? arg.length : 0;
}
當(dāng)然,您可能覺得這兩點(diǎn)在javascript中都可以輕易做到,根本不需要泛型。是的,泛型本身是搭配強(qiáng)類型食用更佳的,在弱類型下沒意義。
在typescript中,泛型有幾種打開方式:
泛型函數(shù):
function someFunction<T>(arg: T) : T {
return arg;
}
console.log(someFunction<number>(123)); // 123
泛型類型:
interface
interface UserInfo{
id: T;
age: number;
}
const userInfo: UserInfo<number> = {
id: 123,
age: 23,
}type
type UserInfo= { // 同上
id: T;
age: number;
}
const userInfo: UserInfo<string> = {
id: '123',
age: 123,
}
泛型類:
class UserInfo {
constructor(private id: T, private age: number) {};
getId(): T {
return this.id;
}
}
我們也可以給類型變量加上一些約束。
泛型約束
有時(shí)編譯器不能確定泛型里面有什么屬性,就會(huì)出現(xiàn)報(bào)錯(cuò)的情況。
function logLength<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
解決方法是加上泛型約束。
interface TypeWithLength {
length: number,
}
function logLength<T extends TypeWithLength>(arg: T): T {
console.log(arg.length); // ok
return arg;
}
6. Typescript高級(jí)篇
6.1 高級(jí)類型
交叉類型:
交叉類型是將多個(gè)類型合并為一個(gè)類型。
interface typeA {
a?: number,
}
interface typeB {
b?: number,
}
let value: typeA & typeB = {};
value.a = 1; // ok
value.b = 2; // ok
聯(lián)合類型:
聯(lián)合類型表示變量屬于聯(lián)合類型中的某種類型,使用時(shí)需要先斷言一下。
interface TypeA {
a?: number,
}
interface TypeB {
b?: number,
}
const value: TypeA | TypeB = {};
(value).a = 1; // ok
6.2 類型別名 type
類型別名可以給一個(gè)類型起個(gè)新名字。類型別名有時(shí)和接口很像,但是可以作用于原始值,聯(lián)合類型,元組以及其它任何你需要手寫的類型。可以將type看做存儲(chǔ)類型的特殊類型。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
...
6.3 is
is關(guān)鍵字通常組成類型謂詞,作為函數(shù)的返回值。謂詞為?parameterName is Type這種形式,?parameterName必須是來自于當(dāng)前函數(shù)簽名里的一個(gè)參數(shù)名。
function isFish(pet: Fish | Bird): pet is Fish {
return (pet).swim !== undefined;
}
這樣的好處是當(dāng)函數(shù)調(diào)用后,如果返回true,編譯器會(huì)將變量的類型鎖定為那個(gè)具體的類型。
例如:
if (isFish(pet)) {
pet.swim(); // 進(jìn)入這里,編譯器認(rèn)為pet是Fish類型。
} else {
pet.fly(); // 進(jìn)入這里,編譯器認(rèn)為pet是Bird類型。
}
6.4 keyof
keyof為索引類型查詢操作符。
interface Person {
name: string;
age: number;
}
type IndexType = keyof Person; // 'name' | 'age'
這樣做的好處是使得編譯器能夠檢查到動(dòng)態(tài)屬性的類型。
function pick<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(key => obj[key]);
}
console.log(pick(person, ['name', 'age'])); // [string, number]
6.5 聲明合并
為什么需要聲明合并呢?
我們思考一下,在javascript中,一個(gè)對(duì)象是不是可能有多重身份。
例如說,一個(gè)函數(shù),它可以作為一個(gè)普通函數(shù)執(zhí)行,它也可以是一個(gè)構(gòu)造函數(shù)。同時(shí),函數(shù)本身也是對(duì)象,它也可以有自己的屬性。
所以這注定了typescript中的類型聲明可能存在的復(fù)雜性,需要進(jìn)行聲明的合并。
合并接口
最簡單也最常見的聲明合并類型是接口合并。從根本上說,合并的機(jī)制是把雙方的成員放到一個(gè)同名的接口里。
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
接口的非函數(shù)的成員應(yīng)該是唯一的。如果它們不是唯一的,那么它們必須是相同的類型。如果兩個(gè)接口中同時(shí)聲明了同名的非函數(shù)成員且它們的類型不同,則編譯器會(huì)報(bào)錯(cuò)。
對(duì)于函數(shù)成員,每個(gè)同名函數(shù)聲明都會(huì)被當(dāng)成這個(gè)函數(shù)的一個(gè)重載。同時(shí)需要注意,當(dāng)接口?A與后來的接口?A合并時(shí),后面的接口具有更高的優(yōu)先級(jí)。
合并命名空間
Animals聲明合并示例:
namespace Animals {
export class Zebra { }
}
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
等同于:
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Zebra { }
export class Dog { }
}
命名空間與類和函數(shù)和枚舉類型合并
類與命名空間的合并:
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel { }
}
函數(shù)與命名空間的合并:
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}
console.log(buildLabel("Sam Smith"));
此外,類與枚舉、命名空間與枚舉等合并也是可以的,這里不再話下。
6.6 聲明文件
聲明文件通常是以.d.ts結(jié)尾的文件。
如果只有ts、tsx文件,那么其實(shí)不需要聲明文件。聲明文件一般是在用第三方庫的時(shí)候才會(huì)用到,因?yàn)榈谌綆於际莏s文件,加上聲明文件之后,ts的編譯器才能知道第三庫暴露的方法、屬性的類型。
聲明語法:
declare var、declare let、declare const聲明全局變量// src/jQuery.d.ts
declare let jQuery: (selector: string) => any;declare function?聲明全局方法declare function jQuery(selector: string): any;declare class聲明全局類// src/Animal.d.ts
declare class Animal {
name: string;
constructor(name: string);
sayHi(): string;
}declare enum?聲明全局枚舉類型declare enum Directions {
Up,
Down,
Left,
Right
}declare namespace?聲明(含有子屬性的)全局變量// src/jQuery.d.ts
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
}interface和type聲明全局類型interface AjaxSettingsInterface {
method?: 'GET' | 'POST'
data?: any;
}
type AjaxSettingsType = {
method?: 'GET' | 'POST'
data?: any;
}export?導(dǎo)出變量在聲明文件中只要用到了export、import就會(huì)被視為模塊聲明文件。模塊聲明文件中的declare關(guān)鍵字不能聲明全局變量。
// types/foo/index.d.ts
export const name: string;
export function getName(): string;
export class Animal {
constructor(name: string);
sayHi(): string;
}
export enum Directions {
Up,
Down,
Left,
Right
}
export interface Options {
data: any;
}對(duì)應(yīng)的導(dǎo)入和使用模塊應(yīng)該是這樣:
// src/index.ts
import { name, getName, Animal, Directions, Options } from 'foo';
console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
data: {
name: 'foo'
}
};export namespace導(dǎo)出(含有子屬性的)對(duì)象// types/foo/index.d.ts
export namespace foo {
const name: string;
namespace bar {
function baz(): string;
}
}// src/index.ts
import { foo } from 'foo';
console.log(foo.name);
foo.bar.baz();export default?ES6 默認(rèn)導(dǎo)出// types/foo/index.d.ts
export default function foo(): string;// src/index.ts
import foo from 'foo';
foo();export =?commonjs 導(dǎo)出模塊// 整體導(dǎo)出
module.exports = foo;
// 單個(gè)導(dǎo)出
exports.bar = bar;在 ts 中,針對(duì)這種模塊導(dǎo)出,有多種方式可以導(dǎo)入,第一種方式是?
const ... = require:// 整體導(dǎo)入
const foo = require('foo');
// 單個(gè)導(dǎo)入
const bar = require('foo').bar;第二種方式是
import ... from,注意針對(duì)整體導(dǎo)出,需要使用?import * as來導(dǎo)入:// 整體導(dǎo)入
import * as foo from 'foo';
// 單個(gè)導(dǎo)入
import { bar } from 'foo';第三種方式是?
import ... require,這也是 ts 官方推薦的方式:// 整體導(dǎo)入
import foo = require('foo');
// 單個(gè)導(dǎo)入
import bar = foo.bar;export as namespace?庫聲明全局變量既可以通過?
亚洲色图欧洲 | 五月激情丁香 | 青娱乐精品在线观看视频 | 成人片无码免费 | 操逼黄色片 |
