TypeScript 復習
基本的な型
$ npx create-react-app <プロジェクト名> --template typescript
/* eslint-disable @typescript-eslint/no-unused-vars */ /** TypeScriptの基本の型 */ // boolean let bool: boolean = true; // number let num: number = 0; // string let str: string = "A"; // Array let arr: Array<number> = [0, 1, 2]; let arr1: Array<string> = ["A", "B", "C"]; let arr2: number[] = [0, 1, 2]; // tuple let tuple: [number, string] = [0, "A"]; // any let any1: any = false; // void 何も返却値がないことを表す const funcA = () => { const test = "TEST"; }; // null let null1: null = null; // undefined let undefined1: undefined = undefined; // object let obj1: object = {}; let obj2: {} = {}; let obj3: { id: number; name: string } = { id: 0, name: "AAA" };
引数の型指定
export const Practice1 = () => { const calcTotalFee = (num: number) => { const total = num * 1.1; console.log(total); }; const onClickPractice = () => calcTotalFee(1000); return ( <div> <p>練習問題:引数の型指定</p> <button onClick={onClickPractice}>練習問題1を実行</button> </div> ); };
引数の戻り値の型指定
export const Practice2 = () => { const calcTotalFee = (num: number): number => { const total = num * 1.1; return total; }; const onClickPractice = () => calcTotalFee(1000); return ( <div> <p>練習問題:引数の戻り値の型指定</p> <button onClick={onClickPractice}>練習問題2を実行</button> </div> ); };
変数の型指定
export const Practice3 = () => { const calcTotalFee = (num: number): number => { const total = num * 1.1; return total; }; const onClickPractice = () => { let total: number = 0; total = calcTotalFee(1000); console.log(total); }; return ( <div> <p>練習問題:変数の型指定</p> <button onClick={onClickPractice}>練習問題3を実行</button> </div> ); };
型指定しない場合
import axios from "axios"; import { useState } from "react"; import "./styles.css"; import { Todo } from "./Todo"; export default function App() { const [todos, setTodos] = useState<any>([]); const onClickFetchData = () => { axios.get("https://jsonplaceholder.typicode.com/todos").then((res) => { setTodos(res.data); }); }; return ( <div className="App"> <button onClick={onClickFetchData}>データ取得</button> {todos.map((todo) => ( <Todo title={todo.title} userid={todo.userid} /> ))} </div> ); }
export const Todo = (props) => { const { title, userid } = props; return <p>{`${title}(ユーザ:${userid})`}</p>; };
エラーに気づけず意図した通りにデータを取得できない
型指定する場合
import axios from "axios"; import { useState } from "react"; import "./styles.css"; import { Todo } from "./Todo"; type TodoType = { userId: number; id: number; title: string; completed: boolean; }; export default function App() { // 配列の型指定 Array<> const [todos, setTodos] = useState<Array<TodoType>>([]); const onClickFetchData = () => { axios .get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos") .then((res) => { setTodos(res.data); }); }; return ( <div className="App"> <button onClick={onClickFetchData}>データ取得</button> {todos.map((todo) => ( <Todo title={todo.title} userid={todo.userId} /> ))} </div> ); }
propsの型指定
import axios from "axios"; import { useState } from "react"; import "./styles.css"; import { Todo } from "./Todo"; type TodoType = { userId: number; id: number; title: string; completed: boolean; }; export default function App() { const [todos, setTodos] = useState<Array<TodoType>>([]); const onClickFetchData = () => { axios .get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos") .then((res) => { setTodos(res.data); }); }; return ( <div className="App"> <button onClick={onClickFetchData}>データ取得</button> {todos.map((todo) => ( <Todo key={todo.id} title={todo.title} userId={todo.userId} completed={todo.completed} /> ))} </div> ); }
type TodoType = { userId: number; title: string; // 必須でない場合は?をつける completed?: boolean; }; export const Todo = (props: TodoType) => { const { title, userId, completed = false } = props; const completeMark = completed ? "[完]" : "[未]"; return <p>{`${completeMark} ${title}(ユーザ:${userId})`}</p>; };
型をまとめる
$ mkdir ./src/types $ touch ./src/types/todo.ts
共通して使用する型を定義
export type TodoType = { userId: number; id: number; title: string; completed: boolean; };
Pickで使用したい型のみを指定する
import { TodoType } from "./types/todo"; export const Todo = ( props: Pick<TodoType, "userId" | "title" | "completed"> ) => {};
もしくは、Omitで必要ない型を指定する
import { TodoType } from "./types/todo"; export const Todo = (props: Omit<TodoType, "id">) => {};
定義した型はimportで読み込む
import axios from "axios"; import { useState } from "react"; import "./styles.css"; import { Todo } from "./Todo"; import { TodoType } from "./types/todo"; export default function App() { const [todos, setTodos] = useState<Array<TodoType>>([]); const onClickFetchData = () => { axios .get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos") .then((res) => { setTodos(res.data); }); }; return ( <div className="App"> <button onClick={onClickFetchData}>データ取得</button> {todos.map((todo) => ( <Todo key={todo.id} title={todo.title} userId={todo.userId} completed={todo.completed} /> ))} </div> ); }
関数コンポーネントの型定義
$ touch ./src/Text.tsx
Functional Component(FC)
import { FC } from "react"; type Props = { color: string; fontSize: string; }; export const Text: FC<Props> = (props) => { const { color, fontSize } = props; return <p style={{ color, fontSize }}>テキストです</p>; };
FCは暗黙的にchildrenを受け取るため、reactのバージョン18まではVFCを使用する
VFCは暗黙的にchildrenを含まないFunctional Componentの型指定
import { VFC } from "react"; type Props = { color: string; fontSize: string; }; export const Text: VFC<Props> = (props) => { const { color, fontSize } = props; return <p style={{ color, fontSize }}>テキストです</p>; };
import { VFC } from "react"; import { TodoType } from "./types/todo"; export const Todo: VFC<Omit<TodoType, "id">> = (props) => { const { title, userId, completed = false } = props; const completeMark = completed ? "[完]" : "[未]"; return <p>{`${completeMark} ${title}(ユーザ:${userId})`}</p>; };
import axios from "axios"; import { useState } from "react"; import "./styles.css"; import { Todo } from "./Todo"; import { TodoType } from "./types/todo"; import { Text } from "./Text"; export default function App() { const [todos, setTodos] = useState<Array<TodoType>>([]); const onClickFetchData = () => { axios .get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos") .then((res) => { setTodos(res.data); }); }; return ( <div className="App"> <Text color="red" fontSize="18px" /> <button onClick={onClickFetchData}>データ取得</button> {todos.map((todo) => ( <Todo key={todo.id} title={todo.title} userId={todo.userId} completed={todo.completed} /> ))} </div> ); }
オプショナルチェイニング
$ touch ./src/types/user.ts $ touch ./src/UserProfile.tsx
export type User = { name: string; hobbies?: Array<string>; };
import { VFC } from "react"; import { User } from "./types/user"; type Props = { user: User; }; export const UserProfile: VFC<Props> = (props) => { const { user } = props; return ( <dl> <dt>名前</dt> <dd>{user.name}</dd> <dt>趣味</dt> // オプショナルチェイニング hobbiesに値が入っていなくてもエラーを吐かない <dd>{user.hobbies?.join(" / ")}</dd> </dl> ); };
import "./styles.css"; import { UserProfile } from "./UserProfile"; import { User } from "./types/user"; const user: User = { name: "Bob", hobbies: ["映画", "ゲーム"] }; export default function App() { return ( <div className="App"> <UserProfile user={user} /> </div> ); }