TypeScriptとは?
TypeScriptはReactなどと一緒に使うことが可能で、JavaScriptを拡張したものです。
TypeScriptを使うことで、型の定義ができたりするのでメリットも大きいです。
筆者が学んだことを皆さんにお伝えできたらと思ってます。
メリット・デメリット
メリット
- 型定義によるチーム開発の円滑化
- バグの事前検知
- VSCodeの自動保管
これらが挙げられます。どういう値なのか、どういうプロパティが存在するかなどを型定義しているとわかりやすいのでチーム間での開発が円滑に進むほか、JavaScriptよりも型で定義している方がバグの発見をしやすくなります。
また、VSCodeでの自動保管も効きやすくなるのでおすすめです。
デメリット
- 型の記述が面倒
- JavaScriptで特有な柔軟な書き方ができない
通常のJavaScriptで記述するよりもコードが長くなってしまうので、めんどくなります。
また、JavaScriptでは簡単に書けたものが型を定義することで一手間がかかってしまうところもあります。
必ず入れなきゃいけない!という訳ではないので、開発する際のプロジェクトに応じて導入するかどうかを決めた方がいいかもしれないですね。
それでは早速TypeScriptの書き方をご紹介します。
導入
既にプロジェクトを立ち上げ済みであればこちらから
npm install -g typescript
プロジェクトがまだの場合はViteを使って作成します。
雛形を作成するだけであればコマンド一発でReactが動作する環境を準備してくれます。
npm create vite@latest vite-practice -- --template react-ts
— — template[テンプレート名]でテンプレートを指定できます。npmのバージョンが7系以上の場合は–templateの直前に–が追加で必要なので注意してください。
プリミティブ型
まずはプリミティブ型からです。
プリミティブというのは文字、数値、真偽値、Bigint、null、undefindが挙げられます。
// 文字列
let str: string = 'hello';
str = 'Bye'
// 数値
let num: number = 102;
// Bigint
let bigNum: bigint = 103n;
//真偽値
let bool: boolean = true;
//null型
let nullish: null = null;
let nullishUNdefind: undefined = undefined;
特徴としては変数名の右に:(コロン)と型の名前が記述されています。
これにより、その型の値しか入れることはできないですよ!と明示的に示しています。
let str: string = 'hello';
str = 12 // 文字ではなく数値だからエラーになる
例えば:stringで文字型として型を定義したにも関わらず数値を代入しようとしているからエラーになります。
リテラル型
型の中でも特定の値を設定できるものです。
let trueVal: true = true;
let num123: 123 = 123;
let strHello: 'Hello' = 'Hello';
上記のコードはtrueValがtrueだったら、num123という変数が123という数値だったらと特定の値を型として定義しています。
当然true以外、123以外の数値だとエラーになります。
ユニオン型
複数の型を組み合わせて「A型またはB型」のように、またはの意味を表すことができます。
let strOrNum: string | number = 'hello';
strOrNum = 123;
strOrNum = 'hello';
変数名の右にコロンをつけて型を定義する際に、|(パイプ)記号で型をつけるとまたはの意味になります。
strOrNumという変数の型はstringかnumberですよと示しています。
また、型の定義を予め変数に格納することも可能です。
type HelloOrNum = 'Hello' | number;
const hello: HelloOrNum = 'Hello';
type WayOfWeek = | 'Monday' | 'Tuesday'
const day: WayOfWeek = 'Monday';
先頭にtypeをつけてHelloOrNumという変数にHelloという文字列とnumber型を格納します。
ここで定義したHelloOrNumを別の変数のコロンの右に記述することでhelloという変数の型はHelloという文字列かnumber型のどちらかになります。
※先頭のパイプについては飾りなのでなくても大丈夫ですが、改行した時のコードの見栄えが良くなります。
オブジェクトと配列の型定義
配列
:型定義の右に[](空の配列)を記述するとその配列の型を表します。
ユニオン型と一緒に組み合わせることも可能です。
const numberArry: number[] = [1, 2, 3];
const stringArry: string[] = ["Taro", 'Hanako'];
//ユニオン型との組み合わせ
const numOrStr:(string | number)[] = ["Hello", 10]
オブジェクト
オブジェクトでキーの値の型を定義します。
それを変数に格納して別のオブジェクトに適用すると型を比較して一致してるか判定してくれます。
type Person = { name: string, age: number }
const Object1: Person = { name: "Taro", age: 10 }
配列の中にオブジェクトが入ってるパターンにも有効です。
const obj2:{name: string, age: number}[] = [
{ age: 15, name: "Takahashi" },
{ age: 15, name: "Tanaka" },
{ age: 15, name: "Sato" },
]
これだとnameとageが格納されていないとエラーになるので、キーの右に?をつけることであってもなくても問題ない状態になります。
const obj2:{name: string, age?: number}[] = [
{ name: "Takahashi" },
{ age: 15, name: "Tanaka" },
{ name: "Sato" },
]
本来ageがないとエラーになるところを?つけたおかげでageがなくてもエラーにはなりません。
readOnly(読み取り専用)
readonlyはプロパティ毎に読み取り専用にする記述。値の上書きができません。
type NameType = {
readonly name: string,
age: number
}
const profile: NameType = {
name: 'ryuta',
age: 29
}
profile.name = 'りゅうた; // 値の上書きはできないのでエラーになります。
as const(まとめて読み取り専用)
readonlyはプロパティ毎に読み取り専用ですが、as constはオブジェクトと配列に対してまとめて読み取り専用にする記述です。
// オブジェクト
const profile = {
name: 'ryuta',
age: 29
} as const;
profile.name = 'りゅうた'; // 上書きできないのでエラー
// 配列
const array = ['hoge1','hoge2','hoge3'] as const;
array.push('hoge4')'配列1',
型エイリアスについて
これまで度々出てきていたtypeについてです。
type文を用いることで型に別名をつけることができます。これを型エイリアスと言います。
type User = {
age: number,
number: string
}
const user: User = { name: "Taro", age: 20 }
type文を記述する際は必ずパスケルケースを使ってください。
パスカルケースは先頭を大文字から始めて単語ごとに大文字にしていくこと 例) UserName
キャメルケースは先頭を小文字から始めて単語ごとに大文字にしていくこと 例) userName
type UserName = string;
type UserAge = number;
type UserHobbies = "soccer" | "tennis" | "baseball"
type User = {
name: UserName,
age: UserAge,
hobby: UserHobbies
};
const userObj: User = {
name: "Taro",
age: 18,
hobby: "soccer"
}
このようにtype文で定義した型を別のtype文のオブジェクトの値に設定することも可能です。
関数の型定義
引数に型を定義して実行したときの引数の型をチェックします。
function sum(x: number, y: number){
return x + y;
}
const result = sum(1, 2);
console.log(result) // 3
引数の右にコロン型名で明示的に型を示すことも可能です。
また、型エイリアスを用いて事前に準備することも可能です。
const sum = (x: number, y: number):number => x + y;
const result2 = sum(10, 20);
//型エイリアスを用いたパターン
type Sum = (x: number, y: number):number => x + y;
const sum2: Sum = (x, y) => {
return x + y;
};
console.log(sum2(1, 2)) // 3
:voidとした場合は値を何も返さないよという意味になります。下記の記述だとreturnで返しているのにエラーになってしまいます。
function sum1(x: number, y: number ):void {
return x + y;
}
console.log(sum1(1, 2));
ジェネリクス型
ジェネリクスは、型を引数のように取り扱うことです。
const genericFunc = <T>(arg: T): T => {
return arg;
}
console.log(genericFunc<number>(123));
console.log(genericFunc<string>('りゅうた'));
関数を実行する際の関数名に右に括弧を設けてその中に型を定義することで、実行元のTの部分に渡ることになるので、引数の値と型をチェックします。
ジェネリクスとtypeの組み合わせ
type ObjectG = {
id: number,
name: string
}
const genericFunc = <T extends ObjectG>(arg: T): void => {
console.log(arg);
}
genericFunc<ObjectG>({
id: 1,
name: 'りゅうた'
})
<T extends ObjectG>この記述はObjectGの中にある型を参照するように制約されています。
関数実行時に <ObjectG>
を記述しているのはジェネリック型引数を明示的に指定していますが、実際には省略しても TypeScript の型推論機能により同じ型が適用されます。
React向きの書き方
関数コンポーネントの型定義
コンポーネント名の右に:React.FCと追加します。
import React from "react";
import Text from "./Test"
const Example: React.FC = () => {
return(
<>
<Test /> // Testです
</>
)
}
const Test: React.FC = () => {
return(
<>
<h1>Testです</h1>
</>
)
}
propsの型定義
import React from "react";
import Text from "./Test"
const Example: React.FC = () => {
return(
<>
<Test text="TypeScript" number={10}/>
</>
)
}
type TestProps = {
text: string,
number: number,
children?: React.ReactNode
}
const Test: React.FC<TestProps> = (props) => {
return(
<>
<h1>{props.text}{props.number}</h1>
</>
)
}
親コンポーネントから子コンポーネントに渡したprops名でtypeを使って予め型の定義をします。
ここでは親コンポーネントからtextとnumberというpropsが渡ってきてその2つを子コンポーネントで型エイリアスにより定義します。
コンポーネントについてはReact.FCをつけると関数コンポーネントでお伝えしましたが、その右に<TestProps>と事前に定義した型エイリアスの名前をつけます。
こうすることで親から渡ってきたコンポーネントの型定義をしています。
childreは以前、暗黙的な型定義により記述しなくてもよかったのですが、React18からは親コンポーネントから渡ってきたchildrenは明示的にする必要があります。
React.FCとしている部分をFCと省略することも可能です。また、propsとしている部分も分割代入にすると可読性が上がるので基本的には分割代入にしましょう。
import { FC } from "react";
import Text from "./Test"
const Example: FC = () => {
return(
<>
<Test text="TypeScript" number={10}/>
</>
)
}
import { FC } from "react";
type TestProps = {
text: string,
number: number,
children?: React.ReactNode
}
const Test: React.FC<TestProps> = ({text, number}) => {
return(
<>
<h1>{props.text}{props.number}</h1>
</>
)
}
OmitとPickで効率的に管理する
type TodoType = {
userId: number;
id: number;
title: string;
completed: boolean;
};
export default TodoType;
import { FC } from "react";
import TodoType from "./types/todo";
const Todo: FC<Omit<TodoType, "id">> = ({ title, userId, completed = false,}) => {
const completeMark = completed ? "[完]" : "[未]";
return (
<>
<p>{`${completeMark}${title}: ${userId}`}</p>
</>
);
};
export default Todo;
typeで定義した型を同階層のtypesフォルダの中のtype.tsの中に格納し、importしています。
propsの型定義を行うので変数名の右のコロンに続いてFC<Omit<TodoType, "id">>
と記載している部分は第一引数の型に対して第二引数に指定したものを省略するという意味になります。
上記の例でいうとpropsとしてtitle, userId, completedを受け取っているのでidはないです。ただ、todo.tsの中で定義している型の中にidがあるのでこれだけ省きたい。
そういう時に使えます。
Pickもご紹介します。
const Todo: FC<Pick<Todotype, "title" | "userId" | "completed">> =
Pickは抽出になるのでTodoTypeの中からpropsで渡ってきた値の型を抽出して型定義することができます。
記述量を考えるとOmitで定義する方が効率が良いかもしれません.
まとめ
いかがでしたか?基本的なTypeScriptの記述方法をお伝えしましたが、記述の仕方はたくさんあるのでつまづいたらこちらの記事を再度見ていただくか、ご自身でググったりしてみてください。
最後までご覧いただき、ありがとうございます。
ご不明点やご相談、お仕事の依頼はいつでもお気軽にご連絡ください。
コメント