はじめに
この記事は私がTypeScriptについて学んだ内容を
TypeScript①(基礎)
TypeScript②(型の機能)
TypeScript③(ユーティリティタイプ)
の3回に分けて網羅的に紹介していきます。
①(基礎)では導入方法と様々な型について簡単な紹介
②(型の機能)では型が実際にどういう働きをするのかについて
③(ユーティリティタイプ)では型変換を容易にするユーティリティ型について
参考リンク
- TypeScript公式ドキュメント
- サバイバルTypeScript 〜実務で使うなら最低限ここだけはおさえておきたいTypeScript入門〜
- TypeScript Deep Dive 日本語版
- 実践TypeScript ~ BFFとNext.js&Nuxt.jsの型定義~ 単行本
Typescriptの導入
TypeScriptのインストール
npm install -g typescript
TypeScriptを使用するプロジェクトフォルダを作成し移動したら
以下のコマンドでtsconfig.jsonを作成
tsc --init
以上でTypeScriptの導入できます。
tsconfig.jsonによる型チェックの厳密さの調整
初期状態のstrict: true
の指定では以下のルールが一括で有効になっています。
- alwaysStrict
- noImplicitAny
- noImplicitThi
- strictBindCallApply
- strictFunctionTypes
- strictNullChecks
- strictPropertyInitialization
tscong.jsonに追加ルールを指定して型チェックを弱くする
暗黙的なany型を許可
"noImplicitAny": false
null型に他のプリミティブ型の割り当てを許可
"strictNullChecks": false
状況によって上記のようにfalse指定することによって型チェックを弱める事ができます。
ただし、これはJavaScriptプロジェクトを段階的にTypeScriptに移行するために許容されるべきであり、TypeScriptを利用する目的から外れてしまわないように注意が必要です。
基本の型
プリミティブ型
string
, number
, boolean
, symbol
, bigint
, null
, undefined
があります。
リテラル型
プリミティブ型を細分化したのがリテラル型で、エディターにベタ書きした文字列や数字や真偽値をリテラルを言います。
boolean型
false
またはture
の値を持つ事ができます。
let flag = true;
number型
16進数、10進数、8進数、2進数をサポートしています。
let decimal: number = 42;
let hex: number = oxee;
let octal: number = 0o213;
let binary: number = 0b1001;
string型
文字列を扱います。またダブルクウォート(")、シングルクウォート(')、バッククウォート(`)で囲います。
let name: string = "Jacob";
name = 'Michael';
let myName: string = `my name is ${name}`;
array型
配列型の記述方法は以下の2つがあります。
- 配列に入る値の型の後ろにブラケット
[]
をつける。
let data: number[] = [1, 2, 3];
- Array型で記述する。
let data: Array<number> = [1, 2, 3];
tuple型
配列内の型を指定できます。
let x: [string, number];
x = ["hello", 3]; // OK
x = [3, "hello"]; // Error
x = ["hello", 3, 0]; // Error
any型
any型はどのような値も代入する事ができ、型が不定の時に使われます。
またany型は型チェックを無効にし、コンパイルを通過させることができるので、不定の型でもコンパイルを通してしまう危険な型とも言えます。
unknown型
unknown型はany型と似ていて、どのような値も代入する事ができ、型が不定の時に使われます。
any型と異なる点は、コンパイルを通さないという事です。そのため型安全なanyと呼ばれることもあります。
void型
void型はany型とは逆にundefined
とnull
以外代入することができません。
戻り値を持たない関数の型として利用されます。
null型/undefined型
全ての型はnullまたはundefiendを代入する事ができ、null型、undefiend型というサブタイプの型を得ることができます。
また、nullは意図的にnullを定義した場合にだけ使う事ができ、undefinedは意図的に限らず 変数に何も代入されていない状態を表す。
never型
never型は発生し得ない型を表します。
function log(text: string): never {
throw new Error(text);
}
function roop(): never {
while (true) {}
}
object型
object型はプリミティブ以外の値の型で、引数にオブジェクトのみ受け取る関数に使用します。
const obj = { foo: 'foo' };
const obj2: {} = obj;
高度な型
Intersection Types
IntersectionTypesは複数の型をアンバサンド&
で結合した型です。
type Address = {
City: "Seattle";
state: "WA";
};
type Name = {
first: "Matthew";
last: "Smith";
};
type Profile = Address & Name;
Union Types
複数の型のうち一つが当てはまることをします場合に使用します。
let data: number | string | boolean;
クラス
TypeScriptではクラスのインスタンス化をする際、受け取った引数をコンストラクターからメンバー変数に代入します。
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
const coordinates = new Point(3, 0);
継承
継承(extends)すると継承元のすべてのプロパティとメソッドを受け継ぎ、受け継いだ先で、呼び出しと、追加のメンバーの定義をする事ができます。
class Animal {
run() {
console.log("走る!");
}
}
class Dog extends Animal {
woof(times: number) {
for (let i = 0; i < times; i++) {
console.log("ワン!");
}
}
}
const d = new Dog();
d.run();
d.woof(3);
// 結果
// 走る!
// ワン!
// ワン!
// ワン!
super
を使って継承元のプロパティを上書きする事ができます。
class Base {
greet() {
console.log("こんにちは、いい天気ですね。");
}
}
class Derived extends Base {
greet(name?: string) {
if (name === undefined) {
super.greet();
} else {
console.log(`こんにちは! ${name.toUpperCase()}`);
}
}
}
const d = new Derived();
d.greet(); // こんにちは、いい天気ですね。
d.greet("ウィルさん!"); // こんにちは! ウィルさん!
クラスメンバー修飾子(public・private・protected)
- publicはどこででも参照・実行が可能
- privateは同一のクラスでのみ参照・実行が可能
- protectedは継承先でのみ参照・実行が可能
同一クラス内での検証
class Base {
public name1: string;
protected name2: string;
private name3: string;
constructor() {
this.name1 = "Ichiro";
this.name2 = "Jiro";
this.name3 = "Saburo";
}
greet1() {
console.log(`こんにちは! ${this.name1}`); // OK
}
greet2() {
console.log(`こんにちは! ${this.name2}`); // OK
}
greet3() {
console.log(`こんにちは! ${this.name3}`); // OK
}
}
const human = new Base();
console.log(human.name1); // Ichiro
console.log(human.name2); // error
console.log(human.name3); // error
human.greet1(); // こんにちは! Ichiro
human.greet2(); // こんにちは! Jiro
human.greet3(); // こんにちは! Saburo
継承先での検証
class Base {
public name1: string;
protected name2: string;
private name3: string;
constructor() {
this.name1 = "Ichiro";
this.name2 = "Jiro";
this.name3 = "Saburo";
}
}
class Derived extends Base {
greet01() {
console.log(`こんにちは! ${this.name1}`); // OK (public)
}
greet02() {
console.log(`こんにちは! ${this.name2}`); // OK (protected)
}
greet03() {
console.log(`こんにちは! ${this.name3}`); // error (private)
}
}
const human = new Derived();
human.greet01(); // こんにちは! Ichiro
human.greet02(); // こんにちは! Jiro
human.greet03(); // error
列挙型(Enum
列挙型は、関連する値の集合を編成する方法です。
また数値列挙は自動で0から順に割り振られ、代入した時点から再度割り振られます。
enum Color {
red,
white,
green,
blue,
black = 2,
pink,
yellow,
}
console.log(Color);
// 結果
// {
// '0': 'red',
// '1': 'white',
// '2': 'black',
// '3': 'pink',
// '4': 'yellow',
// red: 0,
// white: 1,
// green: 2,
// blue: 3,
// black: 2,
// pink: 3,
// yellow: 4
// }
文字列列挙は、数字の自動割り当てはありませんが、別ブロックからもプロパティーを追加することが可能です。
enum Env {
USER_API = "abcd",
USER_KEY = "ABCD",
APP_ID = "0123",
}
console.log(Env);
// {
// USER_API: 'abcd',
// USER_KEY: 'ABCD',
// APP_ID: '0123'
// }
enum Env {
PUBLIC_KEY = "abcd0123",
}
console.log(Env);
// {
// USER_API: 'abcd',
// USER_KEY: 'ABCD',
// APP_ID: '0123',
// PUBLIC_KEY: 'abcd0123'
// }