【TypeScript】型定義のちょっとした応用

【TypeScript】型定義のちょっとした応用

TypeScriptにおける型定義のちょっとした応用を備忘録としてまとめておきます。

環境は以下です。

  • macOS Catalina v10.15.7
  • Visual Studio Code v1.67.1 

インターセクション型

〇〇かつ△△のような型を定義したい時、typeエイリアスの場合&を用いて定義できます。

type Engineer = {
  name: string;
  role: string;
};

type Blogger = {
  name: string;
  follower: number;
};

// EngineerかつBloggerの型
type EngineerBlogger = Engineer & Blogger;

// name, role, followerが必要
const taro: EngineerBlogger = {
  name: 'taro',
  role: 'front-end',
  follower: 1000,
};

interfaceの場合はextendsで継承することで定義できます。

interface Engineer {
  name: string;
  role: string;
}

interface Blogger {
  name: string;
  follower: number;
}

// EngineerかつBloggerの型
interface EngineerBlogger extends Engineer, Blogger { };

// name, role, followerが必要
const taro: EngineerBlogger = {
  name: 'taro',
  role: 'front-end',
  follower: 1000,
};

オプショナルプロパティ

存在するかわからないプロパティに対して、型側のプロパティに?をつけることでエラーを起こさずundefinedを返してくれます。

type FetchData = {
  id: number;
  user?: string
}

const fetchData: FetchData = {
  id: 1,
};

console.log(fetchData.user); // undefined
fetchData.user = 'taro'// taroを追加
console.log(fetchData.user);// taro

オブジェクトが複数階層ある場合は以下のようになります。

type FetchData = {
  id: number;
  user?: {
    name?: {
      first: string;
      last: string;
    };
  };
}

const fetchData: FetchData = {
  id: 1
}

// fetchData.userがない場合はundefinedを返す
console.log(fetchData.user?.name?.first) // undefined

Nullish Coalescingで任意の値を返す

以下の例では、fetchData.userがnullもしくはundefinedの場合、「??」の右の値が適用されます。

type FetchData = {
  id: number;
  user?: {
    name?: {
      first: string;
      last: string;
    };
  };
}

const fetchData: FetchData = {
  id: 1,
};

// fetchData.userがない場合は右の値が適用される
const userData = fetchData.user ?? 'ユーザーが見つかりません';
console.log(userData);// ユーザーが見つかりません

条件文を使って型を絞り込む

  • typeofで絞り込む
  • in演算子で絞り込む
  • タグ付きUnion型で絞り込む
  • classを使用する場合 – instanceofで絞り込む

typeofで絞り込む

関数のパラメーターがUnion型の場合はtypeofを使用することで型の条件分岐が可能です。

const toUpperCase = (x: string | number) => {
  if (typeof x === 'string') {
    // string型の場合の処理
    return x.toUpperCase();
  } else {
    // string型以外 = number型の場合の処理
    return x;
  }
};

console.log(toUpperCase('hello'));// HELLO
console.log(toUpperCase(21));// 21

in演算子で絞り込む

複数のtypeエイリアスをUnion型として持っている型を関数のパラメータに指定する場合、以下のようにin演算子で指定したプロパティを持つtypeエイリアスにのみアクセスできるという絞り込みができます。

type Engineer = {
  name: string;
  role: string;
};

type Blogger = {
  name: string;
  follower: number;
};

type NomadWorker = Engineer | Blogger;

const describeProfile = (worker: NomadWorker) => {
  if ('role' in worker) {
    // roleプロパティを持つtype Engineerのプロパティにアクセスできる
    worker.name;
    worker.role;
  }
  if ('follower' in worker) {
    // followerプロパティを持つtype Bloggerのプロパティにアクセスできる
    worker.name;
    worker.follower;
  }
};

タグ付きUnion型で絞り込む

typeエイリアスやinterfaceの共通するプロパティににリテラル型を指定し、それらを使用して条件分岐を行うことが出来ます。

type OkResult = {
  type: 'ok';
  payload: 'ok response';
};

type ErrorResult = {
  type: 'error';
  payload: Error;
};

type Result= OkResult | ErrorResult;

const unwrapResult = (result: Result) => {
  if (result.type === 'ok') {
    return result.payload;
  } else {
    throw result.payload;
  }
}

instanceofで絞り込む

classの場合instanceofで絞り込むことが出来ます

class Dog {
  speak() {
    console.log('bow-wow');
  }
}

class Bird {
  speak() {
    console.log('tweet-tweet');
  }
  fly() {
    console.log('fly');
  }
}

type Pet = Dog | Bird;

const havePet = (pet: Pet) => {
  pet.speak();

  if (pet instanceof Bird) {
    // petがBirdから作られたインスタンスだった場合
    pet.fly(); // Birdのみが持つflyメソッドにアクセスできる
  }
};

havePet(new Bird());
// tweet-tweet
// fly

レストパラメーターに配列やタプルを指定する

レストパラメーターに配列を指定

function advance(...args: number[]) {
  console.log(args);
}

advance(0, 3, 3, 3, 3)// ▶︎(5) [0, 3, 3, 3, 3]

レストパラメーターにタプル型を指定

function advance(...args: [string, number, boolean, ...number[]]) {
  console.log(args);
}

advance('hoge', 21, true, 0, 0)// ▶︎(5) ['hoge', 21, true, 0, 0]

constアサーション

constアサーションを使うとreadonlyがついたリテラル型になり、値を変えようとするとエラーを出してくれます。

const user = {
  name: '太郎',
  role: 'エンジニア',
} as const; //constアサーション

/*--型は以下のようにreadonlyがつく--
const user = {
  readonly name: '太郎',
  readonly role: 'エンジニア',
};
--*/

user.name = '二郎'; //readonlyがつくのでエラーになる

また、オブジェクトが複数階層ある場合でもconstアサーションはすべてのプロパティを固定することが出来ます。

const asia = {
  name: 'アジア',
  japan: {
    name: '日本',
    capitalCity: '東京',
  },
  korea: {
    name: '韓国',
    capitalCity: 'ソウル',
  },
  China: {
    name: '中国',
    capitalCity: '北京',
  },
} as const;

// 以下全てエラーになる
asia.name = 'Asia';
asia.japan = {
  name: '大日本帝国',
  capitalCity: '京都',
};

asia.korea.name = '大韓民国';
asia.China = '中華人民共和国';