[TS] TS 入門 ft. monsters-rolodex
TypeScript 讓我們能夠在編譯階段前,程式撰寫時就能發現,type 的問題造成 JavaScript 執行會出現 error 的地方,雖然步驟會比較多,但是在專案逐漸變大時,能夠給予我們足夠的穩定性,讓我們更能夠快速除錯、修正。
Settings
create react app本身就有指令可以附加typescript
npx create-react-app my-app --template typescript
假設原本就有 react 專案,就要透過以下指令
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
因為不是透過 template,所以我們要自己建立tsconfig.json,下面跟 template 建立的 config 相同
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
接著,把monsters-rolodex的專案改成 typescript 版本。
Practice
首先,先從search-box.component開始,先將副檔名改成.tsx,vscode 的編輯器這時候就會報錯,要一個個去修正。在:後面,我們要放入預期的 type,全部字母都為小寫,例如:string、boolean、number等等。而在一個函式中,我們需要定義他的參數和回傳值的 type,例如: const func: (a: string, b: number, c: boolean) => void = (a, b, c) => {}。
type
// 有兩種方式可以表示object的type,分別是type和interface
type SearchBoxProps = {
className: string,
placeholder?: string, // string or null
onChangeHandler: (a: string) => void,
};
const SearchBox = ({
className,
placeholder,
onChangeHandler,
}: SearchBoxProps) => {
return (
<input
className={`search-box ${className}`}
type="search"
placeholder={placeholder}
onChange={onChangeHandler}
/>
);
};
type 有著 union 的寫作習慣,可以將多種 type 的聯集成一種 type,例如
type CanadianAddress = {
street: string,
province: string,
};
type USAAdress = {
street: string,
state: string,
};
type ItalianAddress = {
street: string,
region: string,
};
type NorthAmericanAddress = CanadianAddress | USAAdress | ItalianAddress;
const Address: NorthAmericanAddress = {
street: "xxossd",
region: "agacc",
state: "bbdqer",
};
interface
相同地,我們也可以使用interface來定義SearchBox的參數類別,interface慣例以大寫 I 作為開頭,interface 和 class components 很像,有著 extends from A and inherit all of A 的機制
interface ISearchBoxProps extends IChangeHandlerProps {
className: string;
placeholder?: string;
}
// 除了透過extends的方式,也可以定義為相同名稱的interface, ISearchBoxProps
interface IChangeHandlerProps {
onChangeHandler: (a: string) => void;
}
const SearchBox = ({
className,
placeholder,
onChangeHandler,
}: ISearchBoxProps) => {
// ...
};
總論來說,如果是物件導向的程式設計,使用 interface;如果是函式導向的程式設計,使用 type。
ChangeEvent
在 React 之中,所有的事件都有預設的 type,因此我們的onchange事件接收的函式定義的 type 較為不同,他需要接收 React 定義的ChangeEvent並放入不同的 HTML 元素,這邊是HTMLInputElement,可能還有 text boxes、dropdowns、radios 等等。
import { ChangeEvent } from "react";
type SearchBoxProps = {
className: string;
placeholder?: string;
onChangeHandler: (event: ChangeEvent<HTMLInputElement>) => void; // ChangeEventHandler<HTMLInputElement>
};
// ...
return (
<input
className={`search-box ${className}`}
type="search"
placeholder={placeholder}
onChange={onChangeHandler}
/>
接著,看到App.tsx
generics(泛型)
在fetch的過程中,我們不知道傳回來的資料格式為何 ,這時候就很適合使用generics的 type,他很常使用<T>代表,然後在回傳的資料也指定為<T>,我們將fetch的函式 type 拉出來放在data.utils.ts
export const getData = async <T>(url: string): Promise<T> => {
const response = await fetch(url);
return await response.json();
};
接著匯入App.tsx,因為我們 fetch 到的資料要使用的有id,name,email,因此我們定義一個Monster的 type,為 Promise 傳回來的資料類型
import { getData } from "./utils/data.util";
export type Monster = {
id: string;
name: string;
email: string;
};
const App = () => {
// ...
useEffect(() => {
const fetchUsers = async () => {
const users = await getData<Monster[]>(
"https://jsonplaceholder.typicode.com/users"
); // getData<Array<Monster>>
setMonsters(users);
};
fetchUsers();
}, []);
// ...
}
useState
在使用 useState 的時候,預設值也需要給定 type,否則會出現never的 type,代表說還未被設定,''預設為 string type
export type Monster = {
id: string;
name: string;
email: string;
};
const [monsters, setMonsters] = useState<Monster[]>([]);
const [filteredMonsters, setFilteredMonsters] = useState(monsters); // inference from monsters
const [searchField, setSearchField] = useState("");
import Monster type
我們在card-list.component和card.component匯入Monstertype,來定義我們的資料
import { Monster } from "../../App";
type CardListProps = {
monsters: Monster[],
};
const CardList = (props: CardListProps) => {
// ...
};
import { Monster } from "../../App";
type CardProps = {
monster: Monster,
};
const Card = ({ monster }: CardProps) => {
// ...
};
結論
在這篇文章,學習了 TypeScript 和 React 搭配的基本,包括type, interface, ChangeEvent, generics,透過這些 type,我們可以完成簡單的 React 網頁。