TypeScriptの型

TypeScript

boolean,number,string

let value: boolean = false;
let count: number = 0;
let inputText:string = 'hello';

オブジェクト

const person: {
    name: string,
    age: number
} = {
    name: 'Sadako',
    age: 25
}

配列

const Vegetable: string[] = ['Potato','Carrot','Eggplant'];
const numbers: number[] = [1, 2, 3, 4, 5];
const booleans: boolean[] = [true, false, true];

Arrayクラスのジェネリクスを使用して書くこともできます。

const numbers: Array<number> = [1, 2, 3, 4, 5];

カスタム型の配列

type TodoType = { title: string; completed: boolean };
const todos: TodoType[] = [{ title: "Learn TypeScript", completed: false }];
const todos: Array<TodoType> = [
  { title: "Learn TypeScript", completed: false }
];

レストパラメータ

function sum(...numbers: number[]): number {
  return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3, 4));  // 出力: 10
function logItems(...items: string[]): void {
  console.log(items);
}

logItems('apple', 'banana', 'cherry');  // 出力: ['apple', 'banana', 'cherry']

↑このようにレストパラメータによって、引数を複数渡せます。

これがレストパラメータでなかった場合、

function printPersonInfo(personInfo: [string, number]) {
  const [name, age] = personInfo;
  console.log(`Name: ${name}, Age: ${age}`);
}

printPersonInfo(["Alice", 30]); // ✅ OK!
printPersonInfo("Alice", 30);   // ❌ エラー!

引数は配列1つだけで[“Alice”, 30]という形で渡さないといけないです。

Tuple型

異なる型を順番に持つ配列を表現します。

const book: [string, number, boolean] = ['philosophy', 2000, false];

Enum型

列挙型

Enum型は数値・文字列の相互変換が可能です。

enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}
let dir: Direction = Direction.Up;

使用例

フォームのステップ管理

enum Step {
  Input,
  Confirm,
  Complete
}

function FormComponent() {
  const [step, setStep] = useState<Step>(Step.Input);

  return (
    <div>
      {step === Step.Input && <p>入力画面</p>}
      {step === Step.Confirm && <p>確認画面</p>}
      {step === Step.Complete && <p>完了画面</p>}
      <button onClick={() => setStep(step + 1)}>次へ</button>
    </div>
  );
}

APIの取得ステータス管理

enum Status {
  Idle,
  Loading,
  Success,
  Error
}

function DataFetcher() {
  const [status, setStatus] = useState<Status>(Status.Idle);

  useEffect(() => {
    setStatus(Status.Loading);
    fetch("/api/data")
      .then(res => res.json())
      .then(() => setStatus(Status.Success))
      .catch(() => setStatus(Status.Error));
  }, []);

  if (status === Status.Loading) return <p>読み込み中...</p>;
  if (status === Status.Error) return <p>エラーが発生しました</p>;

  return <p>データ取得完了</p>;
}

タブ管理(switchで処理)

enum Tab {
  Home,
  Profile,
  Settings
}

function Tabs() {
  const [tab, setTab] = useState<Tab>(Tab.Home);

  function renderTab() {
    switch (tab) {
      case Tab.Home:
        return <p>ホーム</p>;
      case Tab.Profile:
        return <p>プロフィール</p>;
      case Tab.Settings:
        return <p>設定</p>;
    }
  }

  return (
    <div>
      <button onClick={() => setTab(Tab.Home)}>ホーム</button>
      <button onClick={() => setTab(Tab.Profile)}>プロフィール</button>
      <button onClick={() => setTab(Tab.Settings)}>設定</button>
      {renderTab()}
    </div>
  );
}

Union型

let box: number | string = 10;

配列でもUnion型を使用できます。

let boxFunction:(number | string)[] = [6, 'こんにちは'];

Literal型

リテラル型は値をそのまま型として扱うものです。

const orange = 'orange';

ユニオン型と合わせても使えます。

let shoesSize: 'small' | 'medium' | 'large' = 'small';

as const

as constはリテラル型として値を固定(読み取り専用)にします。

const color = "blue" as const;

↑colorの型はstringではなく”blue”(文字列リテラル型)になります。

typeエイリアスとInterface

typeエイリアスもInterfaceもどちらも型を定義するために使い、javascriptでは表示されません。

違いはこちら↓

比較項目interfacetypeエイリアス
拡張性extendsで拡張できる&(交差型)で合成する
再定義可能(自動的にマージされる)不可(同名定義はエラー)
ユニオン型不可可能
プリミティブ型の代入不可(基本的にオブジェクト専用)可能(stringやnumberなどもOK)
使う場面の慣習ライブラリやAPIのオブジェクト定義複雑な型(ユニオン、タプル、条件型など)

interfaceの例

interface User {
  name: string;
  age: number;
}

interface Admin extends User {
  role: string;
}

typeの例

type User = {
  name: string;
  age: number;
};

type Admin = User & {
  role: string;
};

type ID = string | number; // ユニオン型OK

関数型のReactではtypeが主流で、interfaceはclassと組み合わせて使用される場合が多い。

implements

implementsはメソッドやプロパティを必ず持たせるようにします。

abstractとの違い↓

比較項目interfaceabstract class
目的形の定義(構造)形 + 一部実装
プロパティ定義✅ OK✅ OK
メソッド定義✅ OK(中身はダメ)✅ OK(中身あり・なし両方OK)
メソッドの実装❌ できない(構造だけ)✅ できる
継承の方法implementsextends
多重継承✅ OK(複数interface実装できる)❌(1つのクラスしか継承できない)

具体例↓

interface Flyable {
  fly(): void;
}

interface Animal {
  name: string;
  speak(): void;
}

class Bird implements Animal, Flyable {//implementsはカンマで区切って複数書ける
  name: string;
  constructor(name: string) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} がさえずる`);
  }

  fly() {
    console.log(`${this.name} が飛ぶ`);
  }
}

class Dog implements Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} がワンと吠える`);
  }
}

abstractとの大きな違いは構造の定義だけか実装もできるかの違いですが、interfaceはextendsも使えます。(複数も可)

interface HasName {
  name: string;
}

interface CanSpeak {
  speak(): void;
}

interface Person extends HasName, CanSpeak {}

const me: Person = {
  name: "山田",
  speak() {
    console.log("こんにちは!");
  }
};

コールシグネチャ

関数の型を定義するためのinterfaceの書き方です。

interface GreetFunction {
  (name: string): string;
}

const greet: GreetFunction = (name) => {
  return `こんにちは、${name}さん`;
};

console.log(greet("山田")); // こんにちは、山田さん

コンストラクタシグネチャ

newできる関数(クラスなど)の型を定義します。

interface UserConstructor {
  new (name: string): User;
}

class User {
  constructor(public name: string) {}
}

function createUser(Ctor: UserConstructor, name: string): User {
  return new Ctor(name);
}

const user = createUser(User, "佐藤");
console.log(user.name); // 佐藤

関数宣言に型をつける

パラメータには型を定義

戻り値(パラメータの型の後ろにコロンをつけて定義)は型推論でもOK

function caliculate(num1: number, num2: number): number{
     return num1 + num2
}

関数式に型をつける

戻り値は=>で表します。

const caliculateA :(n1: number, n2: number) => number = caliculate;

void型

返り値がない場合に書きます。

function sayGoodbye(): void {
 console.log('bye');
} 

unknown型

型が不明な場合に使いますが、anyよりも安全です。unknown型の値を操作する前に型チェックが必要です。

let value: unknown = 5;
if (typeof value === 'number') {
  console.log(value + 1);
}

never型

値を何も返さない

type Animal = 'dog' | 'cat';

function sound(animal: Animal) {
  switch (animal) {
    case 'dog':
      return 'wan wan';
    case 'cat':
      return 'nyan nyan';
    default:
      // `animal`は絶対に'dog'か'cat'なので、ここに来ることはないはず
      const _exhaustiveCheck: never = animal;
      return _exhaustiveCheck;
  }
}

アロー関数に型をつける

const doubleNumber = num(num: number) => num * 2

または

const doubleNumber: (num: number) => number = num => num * 2;

コールバック関数に型をつける

function doMath(
  a: number,
  b: number,
  callback: (x: number, y: number) => number
): number {
  return callback(a, b);
}

クラスに型をつける

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(this: {name: string}) {
    return `こんにちは、私は${this.name}です。`;
  }
}

const taro = new Person("太郎", 30);
console.log(taro.greet());  // => こんにちは、私は太郎です。
class Person {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  greet(this: Person): void {
    console.log(`こんにちは、私は${this.name}です`);
  }
}

const p = new Person("田中");
p.greet(); // OK

インターセクション型

複数の型を&(アンパサンド)で結合して定義します。

type A = { name: string };
type B = { age: number };

type Person = A & B;

const p: Person = {
  name: "山田",
  age: 30
};//Personはnameとageの両方を持つ必要がある

複数の構造を組み合わせる↓

type HasId = { id: number };
type HasTimestamp = { createdAt: Date };

type Record = HasId & HasTimestamp;

const log: Record = {
  id: 123,
  createdAt: new Date()
};

ジェネリクス

ジェネリクスは型をパラメータとして受け取る仕組みです。

function identity<T>(value: T): T {
  return value;
}

console.log(identity(123));  // 123 (number)
console.log(identity("abc")); // "abc" (string)

タイプガード

型を判別するためのif文などの中での判定方法で、型に応じてコードの振る舞いを変える時に使います。

typeof

function printId(id: number | string) {
  if (typeof id === "string") {
    console.log("文字列ID:", id.toUpperCase());
  } else {
    console.log("数値ID:", id.toFixed(2));
  }
}

in演算子(プロパティの有無)

type Dog = { bark: () => void };
type Cat = { meow: () => void };

function handleAnimal(animal: Dog | Cat) {
  if ("bark" in animal) {
    animal.bark();
  } else {
    animal.meow();
  }
}

instanceof(クラスインスタンスの判定)

instanceofはクラスインスタンスにしか使えません。

class Car {
  drive() { console.log("ブーン"); }
}

class Bike {
  pedal() { console.log("シャカシャカ"); }
}

function move(vehicle: Car | Bike) {
  if (vehicle instanceof Car) {
    vehicle.drive();
  } else {
    vehicle.pedal();
  }
}

ユーザー定義タイプガード(関数で判定)

type Fish = { swim: () => void };//swimは関数型のプロパティ
type Bird = { fly: () => void };

function isFish(pet: Fish | Bird): pet is Fish {
  return "swim" in pet;
}

function handlePet(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim(); // Fish と確定されて安全に使える!
  } else {
    pet.fly();
  }
}

pet is Fish(ユーザー定義タイプガード)がTypeScriptの構文です。

タグ付きユニオン

共通の識別子(= タグ)を持ったユニオン型で、これにより型を安全に絞り込めます。

タグ付きユニオン型の例↓(kindという共通プロパティがタグ)

type Circle = {
  kind: "circle";
  radius: number;
};

type Square = {
  kind: "square";
  sideLength: number;
};

type Shape = Circle | Square;

これを使ってタグ付きユニオンを使う関数↓

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;
  } else {
    return shape.sideLength ** 2;
  }
}

非同期の状態管理にも使えます。

type Loading = { state: "loading" };
type Success = { state: "success"; data: string };
type Error = { state: "error"; error: string };

type FetchState = Loading | Success | Error;

function render(state: FetchState) {
  switch (state.state) {
    case "loading":
      return "読み込み中...";
    case "success":
      return `データ: ${state.data}`;
    case "error":
      return `エラー: ${state.error}`;
  }
}

インデックスシグネチャ

キーの名前が不定だけど、型は同じなオブジェクトに対して型定義をします。

書き方

interface 型名 {
  [キー名: キーの型]: 値の型;
}

使用例

type UserScores = {
  [username: string]: number;
};

const scores: UserScores = {
  alice: 95,
  bob: 82,
  charlie: 78,
};

Lookup型

// Lookup 型の定義
type Lookup<T, K extends keyof T> = T[K];//Tというオブジェクトの中からキーKに対応する型を取り出す。

// Person 型の定義
type Person = {
  name: string;
  age: number;
  isAdmin: boolean;
};

// Lookup 型を使用して、特定のプロパティの型を抽出
type NameType = Lookup<Person, 'name'>; // => string
type AgeType = Lookup<Person, 'age'>;   // => number

インデックス型アクセス

type Person = {
  name: string;
  age: number;
  isAdmin: boolean;
};

// インデックス型アクセスを使って特定のプロパティの型を取得
type NameType = Person['name'];  // => string
type AgeType = Person['age'];    // => number

TypeScriptでプロパティ名を型として取得するにはクオーテーションが必要です。