TypeScript 終極初學者指南

語言: CN / TW / HK

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

下面是我整理的一些初學者必備的一些知識點~

Typescript 簡介

據官方描述: 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]; // 啥都行,回到了 JS

ids.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 number

person = {
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, '[email protected]', 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); // ConardLi

console.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 error
return a;
}

let hello = 'Hello world';
logLength(hello); // 11

let 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 property
return 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); // 11

let 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

# or

yarn 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 null
const 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 的應用場景了。

- EOF -

覺得本文對你有幫助?請分享給更多人

關注「大前端技術之路」加星標,提升前端技能

點贊和在看就是最大的支援 :heart: