最近Next.jsとTypeScriptを使って開発することがあり、ChatGPTやGitHub Copilotのサポートを得ながらなんとなくは実装はできているけど、「理解」ができていないのではと思ったので記事にまとめました。
TypeScriptって何?
まずは公式サイトを見てみます。
https://www.typescriptlang.org/
- Javascriptに追加の構文を加えることで、エディターとの連携をサポートしてエラーの発見を早くする。
- TypeScriptのコードはJavaScriptに変換されて、JavaScriptが動作するところであればどこでも正常に動作する。
- 型推論を用いて、コードを追加することなく優れたツールを提供する。
といった感じになんだか難しいことが書いてありますねぇ。
JavaScriptに型の概念を取り入れたものがTypeScriptとなります。
『型』とは、値の種類のことです。
例えば、人が1
と1
を足してと言われれば2
と答えそうですが、プログラミングの世界では2
なのか11
となのか迷ってしまいます。
const test1 = 1 + 1;
console.log(test1); //2
const test2 = "1" + "1";
console.log(test2); //11
なので型をつけて1
というものが、数字
なのか文字
なのかを定義することでより厳密に処理を行うことができるようなるといった感じです。
この型を定義することができるのがTypeScript
ということですね。
TypeScriptの型について
TypeScriptにおける基本的な型が以下のようにあり、2つに分類することができます。
プリミティブ型
● string型 ・・・文字列
● number型・・・数値
● bigint型・・・数値では扱えない大きな数値
● boolean型・・・真偽値
● Undefind型・・・全ての方のサブタイプ
● Null型・・・全ての方のサブタイプ
● Array型・・・配列
● Tuple型・・・タプル
● Enum型・・・列挙
● Any型・・・型チェック無効化
● Void型・・・型がない
● Never型・・・属する値のない型
● Object型・・・オブジェクト
● 型アサーション・・・値の型情報を上書きする
string
let sports: string = "soccer"
sports = 1
// Type 'number' is not assignable to type 'string'
sports = "baseball"
sportsは文字列と定義しているのに、数値(1)を代入しようとしているのでエラーがでる。
正しく文字列を代入すればエラー無くいけます。
number
let age: number = 33;
age = "33歳です";
//Type 'string' is not assignable to type 'number'.
age = 34;
age
は数値として定義しているのに、文字列を代入しようとしているのでエラーがでる。
bigint
let largeNumber: bigint = 9007199254740991n; // 'n' はこれがbigint型であることを示す
// largeNumber = 9007199254740992; // エラー: 'number' 型の値を 'bigint' 型には割り当てられません
largeNumber = 9007199254740993n; // 正しい使用法
bigintは大きすぎてnumberでは表すことができない数を表現します。
boolean
// OK
let isFlag: boolean = true;
// NG
let isOpen: boolean = "true";
true
か false
を見ています。文字列true
と入れてもダメです。
Array型
const names: string[] = ["山田", "鈴木", "伊藤"];
names.push("高田");
names.push(111);
const names: Array<string> = ["山田", "鈴木", "伊藤"];
names.push("高田");
names.push(111);
型名[] か Array<型名>のどちらかで指定する。
配列の中身には、文字列を指定しているので、names.push(111);
でエラーがでます。
Tuple型
let user: [string, number];
user = ["オカ", 32];
console.log(user);
//["オカ", 32]
console.log(user[1]);
// 32
複数の値を保持することができる。配列によく似ている。
Tuple型では異なる型の要素を持ち、固定された数の要素のみを持つ配列です。
要素の数と数の順序は固定されます。
Enum型
enum Color {
red,
green,
blue
}
let red: number = Color.red;
let green: number = Color.green;
let blue: number = Color.blue;
console.log(red, green, blue);
//
0
1
2
仕様が複雑ですし、他の型の記事など見てもあまり積極的には使う必要はないのかなと思っています。
Any型
let anything: any = "なんでもOK";
anything = 100;
console.log(anything);
//100
anything = true;
console.log(anything);
//true
型の不明な変数に対して使う。なるべく使わないようにする。
Void型
let message = (): void => {
console.log("元気ですか!?");
};
message();
戻り値のない関数のときに、返り値の型としてvoid
を指定する。
NullとUndefined
const val1: null = null;
val1 = 1;
// Cannot assign to 'val1' because it is a constant.
const val2: undefined = undefined;
val2 = "text";
//Cannot assign to 'val2' because it is a constant.
基本的に、単体での使用はなく他の型と組合わせて使うことが多い。
Never
型を持たない方を表すもの。
とありましたがイマイチわからん。
参考になりそうな記事。
https://qiita.com/macololidoll/items/1c948c1f1acb4db6459e
Object
const user: { name: string; age: number } = {
name: "オカ",
age: 20
};
console.log(user.tel);
// Property 'tel' does not exist on type '{ name: string; age: number; }'
未定義のプロパティにアクセスすると警告される。
type / interface
//type
type User = {
name: string;
age: number;
};
const user: User = {
name: "オカ",
age: 31
};
console.log(user);
//interface
interface User {
name: string;
age: number;
}
const user: User = {
name: "オカ",
age: 31
};
console.log(user);
複数の箇所で同じ型を使いまわしたい時に使う。
参考→https://typescriptbook.jp/reference/object-oriented/interface/interface-vs-type-alias
複合的な型
type SampleA = {
str: string;
num: number;
};
type SampleB = {
str: string;
flg: boolean;
};
type SampleC = SampleA & SampleB;
const data: SampleC = {
str: "おーい",
num: 1,
flg: true
};
console.log(data);
//{str: "おーい", num: 1, flg: true}
型と型を&でつなぐことで新しい形を生成できる。
Union
let val1: string | number;
val1 = "おーい";
val1 = 1;
val1 = true; //エラー
type Result = "success" | "error";
const result: Result = "";
パイプラインを使用するパターン。
型アサーション
// 型アサーション
interface Test {
foo: number;
}
const test: Test = {} as Test;
test.foo = 123;
テスト型の変数testを宣言。
Optional
// error
type User = {
name: string;
age: number;
weight?: number;
};
const user: User = {
name: "おか",
age: 31
};
// ok
type User = {
name: string;
age: number;
weight?: number;
};
const user: User = {
name: "おか",
age: 31
};
プロパティが足りていないのでエラーがでるが、weight: number;
をweight?: number;
に書き換えることで任意と指定することができる。
いったんここまでで、型の紹介を終えます。
いやー僕もわからないものが結構あったので、コード書いて理解ふかめていきます。
Generics(ジェネリクス)について
型引数を指定することで、クラスやコンポーネント・関数などを使用する時に、実際に型を決定できる仕組みです。
1つ例を見ていきます。
type Sample<T> = {
value: T;
};
const val1: Sample<string> = {
value: "ハロー"
};
console.log(val1);
// {value: "ハロー"}
const val2: Sample<string> = {
value: true
};
// Type 'boolean' is not assignable to type 'string'
関数名(型)名<型名>となりますジェネリクスを使用する場合には、<T>
のようにTを使うことが多いみたいです。
関数のときのパターンも見てみます。
// functionを使った宣言
const testFunc = function <T>(arg: T): T {
return console.log(arg);
};
testFunc<string>("ハロー");
testFunc<string>(1);
// アロー関数
const testArrow = <T extends {}>(arg: T): T => {
return console.log(arg);
};
testArrow<string>("こんにちは");
testArrow<string>(true);
Utility Typesについて
Utility Typesとは型変換を行うための便利な機能になります。
一度定義した方を元に、場面に合わせて型を変換できます。
Partial<T>
type User = {
name: string;
age: number;
gender: string;
};
type User1 = Partial<User>;
// type User1 = {
// name?: string | undefined;
// age?: number | undefined;
// gender?: string | undefined;
// }
全てOptinalに変換してくれる。
Required<T>
type Todo = {
task?:string;
status?:boolean
}
type Todo1 = Required<Todo>
// type Todo1 = {
// task: string;
// status: boolean;
// }
optionalのプロパティを全て必須のプロパティに変換する。
Readonly
type Todo = {
task?: string;
status?: boolean;
};
type Todo2 = Readonly<Todo>;
const todo: Todo2 = {
task: "掃除",
status: true
};
console.log(todo.task); //掃除
todo.task = "変更不可";
// Cannot assign to 'task' because it is a read-only property.
読み取り専用で、task
は変更不可とでます。
Record<T,U>
type Todo = {
task: string;
status: boolean;
};
type Todo3 = Record<number, Todo>;
const todo3: Todo3 = {
"No.1": {
task: "掃除",
status: true
},
1: {
task: "掃除",
status: true
}
};
プロパティがT型、値がU型の型を作成する。
Pick<T, U>
type Todo = {
task: string;
taskMin: number;
status: boolean;
};
type Todo4 = Pick<Todo, "task" | "taskMin">;
const todo4: Todo4 = {
task: "掃除",
taskMin: 30,
status: true
//errorになる
};
T型に含まれているU型のプロパティを取り出して新しい形を作成する。
Omit<T, U>
type Todo = {
task: string;
taskMin: number;
status: boolean;
};
type Todo5 = Omit<Todo, "task">;
const todo5: Todo5 = {
task: "掃除",
taskMin: 30,
status: false
};
T型に含まれるU型のプロパティを除去した新しい形を作成する。
ReturnType<T>
type Todo = {
task: string;
taskMin: number;
status: boolean;
};
const getTask = (): Todo => {
return todo;
};
type TypeA = ReturnType<typeof getTask>;
// type TypeA = {
// task: string;
// taskMin: number;
// status: boolean;
// }
関数型のTの戻り値の型を取り出す
まとめ
プリミティブ型はなんとなくわかるのですが、それ以外は正直あまり理解できていないので勉強していかねばと思っています。