Photo by Dee @ Copper and Wild on Unsplash
create-react-app 官方有個 TypeScript 初始化懶人包,我們將透過 command line 下載懶人包,並將專案命名為 typescript-react-textbook。
$ npx create-react-app typescript-react-textbook --template typescript
$ cd typescript-react-textbook
對熟悉React的人來說,TypeScript懶人包的資料夾結構與一般的js懶人包沒有太大的差別。除了
.js
檔變成.tsx
檔 多了一個typescript設定檔tsconfig.json
在做任何改動之前,先把專案跑起來,看能否正常啟動:
$ yarn start || npm start
自訂一個元件
接下來要練習,如何用TypeScript製作一個共用元件,並在App.tsx
當中使用。
以下會將游標框起來的部分,做成一個共用的<Link />
元件。
在src
目錄下新建一個Components
的資料夾,並新增Link.tsx
的檔案。
我們先不管型別,一如往常的新建一個functional component。
/*
* src/Components/Link.tsx
*/
import React from 'react';
const Link = ({
href,
content
}) => {
return (
<a
className="App-link"
href={href}
target="_blank"
rel="noopener noreferrer"
>
{content}
</a>
);
};
export default Link;
此時編輯器可能會出現紅色毛毛蟲的警示線,我們先不要理它。
回到App.tsx
,import 這個元件。
Oops,只是import而已,瀏覽器就跳出錯誤訊息。
預設的型別:any
Binding element ‘href’ implicitly has an ‘any’ type.
意思是:你尚未指定href這個props的型別,你是否預設它的型別是any?
any
是TypeScript的型別之一。不論這個變數是什麼,我都吃!
不過any根本違反了使用強型別的理由,除非你在定義時說清楚「我就是要用any」,否則TypeScript會跳出錯誤來提醒你。
我們確實可以敷衍過去,將所有的型別設為any
,編譯器也會放你一馬。
/*
* src/Components/Link.tsx
*/
const Link = (props: {
href: any; content: any;}) => {
return (
<a
className="App-link"
href={props.href}
target="_blank"
rel="noopener noreferrer"
>
{props.content}
</a>
);
};
等等,既然大家都是any
,何苦學TypeScript來為難自己呢 T_T?
any
存在的意義,是讓人在萬不得已的情況下使用。例如有些變數,我們無法預期它會接到什麼值。
const param = JSON.parse(someData);
既然要使用TypeScript,除了特殊案例以外,就請確實的定義型別吧!
/*
* src/Components/Link.tsx
*/
const Link = (props: {
href: string; content: string;}) => {
return (
<a
className="App-link"
href={props.href}
target="_blank"
rel="noopener noreferrer"
>
{props.content}
</a>
);
};
React內建的props怎麼定義型別
props.children是我們在建立元件時,很常用到的變數。
children怎麼定義型別呢?React官方已經幫我們準備好了。
以Functional Component來說,官方有提供React.FunctionComponent<Props>
方便開發者使用,
可以簡寫為React.FC<Props>
。
在React.FC<Props>
當中,官方已經幫我們定義好children
, displayName
, defaultProps
等屬性的型別。
只要標示元件的型別為React.FC<Props>
,即可省去定義這些預設屬性的麻煩。
/*
* src/Components/Link.tsx
*/
const Link: React.FC<{ href: string;
content: string;
}> = (props) => {
const { children } = props;
return (
<a
className="App-link"
href={props.href}
target="_blank"
rel="noopener noreferrer"
>
{props.content}
</a>
);
};
對熟悉強型別語言的人來說,<>
一點都不陌生,就是泛用型別,簡稱泛型。
它的使用情境是:這個Function所帶入的參數,型別可能有所不同。
有時候參數是number,也可能是string,某一天也可能是Object。
反正我不想把這個變數的型別定死,用到的時候再設定它的型別就可以了。
使用者只要在<>
角括號裡面,填入你這次要檢查的型別即可。
例如<string>
,<number>
等等。
// Type = set_the_Type_you_want
function aFuncReturnInput<Type>(input: Type): Type { return input;
}
const isNumber = aFuncReturnInput<number>(30);
const isString = aFuncReturnInput<string>('A Loooooooooog Word');
因此,const Link: React.FC<{}>
的意思是:我宣告了一個React元件Link
,其參數(props)的型別是Object。
在Object裡面,再去詳細定義有哪些必要 / 非必要的key-value。
const Link: React.FC<{ href: string; content: string;}> = (props) => {
return ();
}
抽象化的型別 Type Alias
如過你覺得props的key實在太多了,程式碼不美觀,也可以採用”抽象化型別”的方式來宣告props。
type LinkProps = { href: string; content: string;};
const Link: React.FC<LinkProps> = (props) => {
return ();
}
使用元件
到目前為止,我們終於用Typescript完成一個基礎元件。
回到一開始的目標,我們要將HTML的<a>
標籤,取代成自訂的<Link>
元件。
之前提到,Typescript會檢查必填/非必填的屬性,若是少了必填的屬性,編輯器會出現紅色毛毛蟲提醒你。
Summary
此章節學到了:
- 在尚未定義型別之前,Typescript預設的隱含型別為any
- 宣告Function的方式
- 宣告物件的方式
- 使用
React.FC<Props>
來宣告Functional Component - 將常用的物件抽象化,自訂型別
附件 Attachment
型別宣告
宣告 - 物件
宣告一個”貓”物件,必填屬性有3個。分別是name,gender,age:
const cat: { name: string; gender: string; age: number } = { name: 'mimi',
gender: 'female',
age: 3
};
宣告一個”貓”物件,屬性name,gender為必填,age為非必填:
const cat: {
name: string;
gender: string;
age?: number; // 在 key 後面加上 ? 表示該屬性非必填} = {
name: 'mimi',
gender: 'female'
};
宣告 - Function
宣告一個函式plus,有兩個參數,分別為x, y。x, y的型別皆為數字。 plus會回傳x + y的總和,回傳值也是數字:
const plus = (x: number, y: number): number => { return x + y;
};
plus(5, 5); // 10
將上面的函式plus,改成只有一個參數obj。 obj是一個物件,有兩個key,分別為x, y。 回傳值同樣為x + y的總和:
const plus = (obj: { x: number; y: number;}): number => { return x + y;
};
plus(5, 5); // 10
同上,將參數以解構賦值(object destructuring assignment)的方式來撰寫:
const plus = ({x, y}: { // 將obj直接展開為{x, y} x: number;
y: number;
}): number => {
return x + y;
};
plus(5, 5); // 10
有些function只是執行某個工作,並不會有回傳值。 沒有回傳值時,宣告的方法如下:
const doSomething = ({x, y}: {
x: number;
y: number;
}): void => { // 沒有回傳值: void console.log(x, y);
}
doSomething({5, 5});
宣告 - 自訂抽象化的型別 Type Alias
當專案越來越大的時候,我們會發現,有些屬性會重複出現,例如:
當其他物件要使用同一個商品,我們又要把所有屬性複製貼上:
Typescript可以把重複的部分抽出來,自己定義一個型別。
type Book = {
title: string;
author: string;
publicationDate: string;
publisher: string;
cover: string;
editor: string;
};
這個型別即可重複使用。如果哪天,商品的欄位增加了,會更容易修改程式碼。
const Item: React.FC<Book> = (props) => { return (
<div className="item"></div>
);
};