TypeScript 終極初學者指南

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

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

Typescript 簡介

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

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

Typescript 值得學嗎?

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

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

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

TypeScript 中的類型

原始類型

JavaScript 中,有 7 種原始類型:

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

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, '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); // 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 框架一起使用:

如果你需要一個更自定義的 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>
  );
};

參考

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

code 祕密花園 這裏有最前沿的前端技術、最新的前端消息、最精品的技術文章、最好用的工具推薦、還有一個有趣的作者。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6DAyXFHIMW95FS0f3GyHpA