はじめに
現在の実務環境だとReactで開発をする上で現在はtypeScriptで実装するのがほぼ必須となっている。
typeScriptはJavaScriptに型定義の概念を取り入れた言語。
型を定義することで意図しない変数の扱いで生まれるバグを未然に防ぐことができる。
typeScriptでの開発をすすめる前に型定義の基本をまとめておいた。
typeScriptの型定義の基本
/**typeScriptの基本の型 */
//boolean
let bool: boolean = true;
//number 数値
let num: number = 0;
//string 文字
let str: string = "A";
//Array 配列
let arr1: Array<number> = [0, 1, 2];
let arr2: number[] = [0, 1, 2];
//どちらでもいい
//tuple
let tuple: [number, string] = [0, "A"];
//配列の順番に型を指定する
//any
let any1: any = false;
//どんな型でも適用できる
//void
const funcA = (): void => {
const test = "TEST";
};
//関数の返り値の型指定。voidの場合は返り値なし。
//null
let null1: null = null;
//undefined
let undefined1: undefined = undefined;
//object
let obj1: object = {};
let obj2: { id: number; name: string } = { id: 0, name: "AAA" };
//オブジェクトのプロパティの要素ごとに型を指定することが多い
Reactでの型定義
引数の型指定
引数に型を指定する場合は、引数の括弧の中で型の指定を行う。
引数に渡そうとした値の型が違う場合エラーになる。
jsの場合文字列で数値を与えても、暗黙的に型変換してエラーなく計算できるため意図しない数値の受け渡しに気がつきにくい。
//数値型の引数のみ受け取れる
const func1 = (num: number) => {
const total = num * 1.1;
console.log(total);
};
返却値の型指定
引数の括弧の後ろに型を指定する。
何も返さない場合はvoid型が使える。暗黙的型指定で明示的に型を指定しなくても値を返すことができるが、どんな型を返却するか明示的に指定している方があとから見た人に分かりやすい。
保守性の高いプログラムは他の人が見やすいコードを意識する。
//返却値を数値型で返却することを明示的に指定
const func1 = (num: number): number => {
const total = num * 1.1;
return total;
};
変数の型指定
関数を使って変数に値を代入する場合に、変数にしっかり型が指定してあると関数から帰ってきた値が指定した型と異なる場合にエラーで教えてくれる。
const func1 = (num: number) => {
const total = num * 1.1;
return total.toString();
};
const onClickButton = () => {
let total: number = 0;
total = func1(1000);
//上記の関数から文字列型が帰ってきても、変数に型を指定しておけばエラーがでる
console.log(total);
};
stateの型指定
stateに型を指定する場合useState<型指定>([初期値])
という指定の仕方をする
//文字列型の配列としてstateを定義している場合
const [todos, setTodos] = useState<Array<string>>([]);
取得したデータの型の定義
httpリクエストなどで取得したデータに型を定義する。
コンポーネントのように型を定義して汎用的に使えるようにする。
コード補完機能が便利に使える
例えば配列で取得されるデータが配列で型定義されていると、配列に対して行うことができる処理のコード補完がでたり、オブジェクトのプロパティがコード補完で選択できるようになり打ち間違いなどのミスをなくすことができる。
//取得するデータのプロパティごとにオブジェクトの型を定義しておく
//typeはexportできるので、別のフォルダに型定義をまとめておける
type TodoType = {
userId: number;
id: number;
title: string;
completed: boolean;
};
const [todos, setTodos] = useState<Array<TodoType>>([]);
//axiosで取得したデータに上記で定義したTodoType型を指定する
const onClickFetchData = () => {
axios
.get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos")
.then((res) => {
setTodos(res.data);
});
};
propsの型指定
受け取るpropsが型定義されていると、受け渡すpropsで足りないものがエラーで分かる。
またpropsを追加したり、名称を変えた場合もどこに影響がでるのかすぐに発見することができる。
コンポーネントを分けて開発するReactと非常に相性がよい◎
//exportした型定義を使う場合のimport
//typeを付けることでビルド時に型情報を取り除ける。無くてもエラーにはならない。
import type { TodoType } from "./Types/todo";
/**
export type TodoType = {
userId: number;
id: number;
title: string;
completed?: boolean;
};
*/
//props
export const Todo = (props: TodoType) => {
//propsのプロパティ名を間違っているとエラーがでる。
const { title, userid, completed } = props;
const completeMark = completed ? "[完]" : "[未]";
return <p>{`${completeMark}${title}(ユーザー:${userId})`}</p>;
};
上記の型定義でcompleted?: boolean;
の?箇所は受け取るかどうか分からないpropsの場合のオプショナル。この場合propsとしてcompleted
がなくてもエラーとならない。
PickとOmit
上記のように定義したTodoコンポーネントを使う場合、実際にはTodoTypeのプロパティであるidをpropsとして渡していないため、propsでidが足りないというエラーがでる。
その場合に必要な型のプロパティをPick
とOmit
を使って指定できる。
import type { TodoType } from "./Types/todo";
//Pickは型定義の中から必要なプロパティだけ指定できる
export const Todo = (props: <Pick<TodoType, "title" | "userId" | "completed">>) => {
const { title, userId, completed = false } = props;
const completeMark = completed ? "[完]" : "[未]";
return <p>{`${completeMark}${title}(ユーザー:${userId})`}</p>;
};
//Omitは型定義の中から不要なプロパティだけ指定できる
export const Todo = (props: <Omit<TodoType, "id">>) => {
const { title, userId, completed = false } = props;
const completeMark = completed ? "[完]" : "[未]";
return <p>{`${completeMark}${title}(ユーザー:${userId})`}</p>;
};
コンポーネント自体の型指定
関数コンポーネントの型定義で使うFCとVFC
- FCは暗黙的にpropsにchildrenを含んでいるため、定義していなくてもpropsにchildren利用する際にエラーがでない。
- VFCはpropsにchildrenを含まずに定義する方法(暫定的)
今後ReactのバージョンアップでFCにchildrenを含まないようになるまではVFCを利用する。
import type { VFC } from "react";
type Props = {
color: string;
fontSize: string;
};
//propsの型定義もVFCの後ろに定義する
export const Text: VFC<Props> = (props) => {
const { color, fontSize } = props;
return <p style={{ color, fontSize }}>テキストです</p>;
};
上記に習って先ほどのPropsの型定義を書き直す
一般的にPropsを定義する際はこちらの定義の方法を使う
import type { TodoType } from "./Types/todo";
//Pickは型定義の中から必要なプロパティだけ指定できる
//VFCの後ろにpropsの型を定義する
export const Todo:VFC<Pick<TodoType, "title" | "userId" | "completed">> = (props) => {
const { title, userId, completed = false } = props;
const completeMark = completed ? "[完]" : "[未]";
return <p>{`${completeMark}${title}(ユーザー:${userId})`}</p>;
};
//Omitは型定義の中から不要なプロパティだけ指定できる
export const Todo:VFC<Omit<TodoType, "id">> = (props) => {
const { title, userId, completed = false } = props;
const completeMark = completed ? "[完]" : "[未]";
return <p>{`${completeMark}${title}(ユーザー:${userId})`}</p>;
};
オプショナルチェイニング
Propsで渡される型指定にオプションがある場合、そのPropsが渡されないとそのデータに対して処理しようとした関数があるとエラーになってしまう。
(無いデータに関して処理しようとするため)
下記ではhobbies配列が渡されなかった場合に.join
という配列に対する処理を行おうとしているためエラーとなる。
import type { VFC } from "react";
import type { User } from "./Types/user";
type Props = {
user: User;
};
/**
export type User = {
name: string;
hobbies?: Array<string>;
};
*/
//hobbiesプロパティはオプションになっているため、Propsとして渡されない場合がある
export const UserProfile: VFC<Props> = (props) => {
const { user } = props;
return (
<dl>
<dt>name</dt>
<dd>{user.name}</dd>
<dt>趣味</dt>
<dd>{user.hobbies.join(" / ")}</dd>
</dl>
);
};
上記をこのように書き換えてエラーを回避することができる。
hobbies?.join
繋いでいるドットの前に?を付けるオプショナルチェイニング
?の前までで要素が見つからない場合undefinedになる。
//hobbiesが見つからない場合undefinedを返すためエラーにならない
return (
<dl>
<dt>name</dt>
<dd>{user.name}</dd>
<dt>趣味</dt>
<dd>{user.hobbies?.join(" / ")}</dd>
</dl>
);
EventHandlerの型定義
onChangeやonClickなどのイベントにも型を定義する。
エディターが賢いのでマウスをホバーして暗黙的型推論で型を確認できるので、イベントを使う場合にはそこを参考にすると楽。
よく引数に使うeventオブジェクトに対するコード補完が効くので便利。
event.target.value
など。
type Props = {
id: string;
label: string;
text: string;
onChange: ChangeEventHandler<HTMLInputElement>;
}
複合的な型:intersection(交差型)
型を結合して新しい型を作成する
type SampleA = {
str: string;
num: number;
};
type SampleB = {
str: string;
flg: boolean;
};
type SampleC = SampleA & SampleB;
/**
SampleC = {
str: string;
num: number
flg: boolean;
}
*/
複合的な型:union(合併型・共用体型)
複数の型を指定する。
下記val1
はstring
型かnumber
型を格納できる。
let val1: string | number;
最後に
今までJavaScriptであると実際に動かしてみて発見するエラーが多かった。
デバッグする際もconsole.logなどを使って値を確認することで原因を調べたが、typeScriptを利用することで未然にバグを発見することができる。
この辺の型安全に開発を進めるという概念は今までJavaで開発していたので非常にしっくりくる。
実際開発を進めて、今後も疑問点などをまとめていきたい。