🧩

TypeScriptのTips集

2020/12/18に公開
1

この記事は TypeScript Advent Calendar 2020 の 17 日目です。
今回は TypeScript のTipsをいくつかあげていきたいと思います!
割と基本的なものが多いので、普段から TypeScript をがっつり書いている方に取ってはすでに知っているものが多いかもしれません。ただ、意識しないとあまり使わなかったり、TypeScript の経験がない方に取っては新鮮なものもあるのでは?と思ったので書いてみました。

keyof

オブジェクトのキーを union 型に

type Post = {
  id: number;
  title: string;
  content: string;
};

type PostKey = keyof Post;
// type PostKey = "id" | "title" | "content"

使うときはこんな感じでしょうか

const sortBy = <T extends object, K extends keyof T>(
  objects: T[],
  key: K
): T[] => {
  return objects.sort((a, b) => a[`${key}`] - b[`${key}`]);
};

const posts = [
  { id: 1, title: 'test1' },
  { id: 3, title: 'test3' },
  { id: 2, title: 'test2' },
];

console.log(sortBy(posts, 'id'));
// [
//   { id: 1, title: 'test1' },
//   { id: 2, title: 'test2' },
//   { id: 3, title: 'test3' }
// ]

console.log(sortBy(posts, 'name'));
// Argument of type '"name"' is not assignable to parameter of type '"id" | "title"'

lookup

例えば API の返り値が次の型のようなとき
(GitHub API を参考にしています)
以下のように type のプロパティの型を取り出すことができる

type Response = {
  repository: {
    id: string;
    homepageUrl: string;
    issues: {
      edges: {
        node: {
          author: {
            url: string;
          };
          body: string;
        };
      };
      totalCount: number;
    }[];
  };
};

type Issues = Response['repository']['issues'];
// type Issues = {
//     edges: {
//         node: {
//             author: {
//                 url: string;
//             };
//             body: string;
//         };
//     };
//     totalCount: number;
// }[]

type IssueTotalCount = Issues[0]['totalCount'];
// type IssueTotalCount = number

utility

Partial

フィールド全てが Optional になる

type Post = {
  id: number;
  title: string;
};
type PartialPost = Partial<Post>;
// type PartialPost = {
//     id?: number;
//     title?: string;
// }
const post1: PartialPost = { id: 1 };

Required

フィールド全てが必須になる

type Post = {
  id: number;
  title: string;
  userId?: number;
};
type RequiredPost = Required<Post>;
// type RequiredPost = {
//     id: number;
//     title: string;
//     userId: number;
// }

Readonly

フィールド全てが 読み込み専用 になる

type Post = {
  id: number;
  title: string;
};

type ReadonlyPost = Readonly<Post>;
// type ReadonlyPost = {
//   readonly id: number;
//   readonly title: string;
// };
const readonlyPost: ReadonlyPost = { id: 1, title: 'readonly' };
readonlyPost.title = 'update';
// Cannot assign to 'title' because it is a read-only property.

Pick

指定した key だけ取り出す

type Post = {
  id: number;
  title: string;
};

type PickPost = Pick<Post, 'id'>;
// type PickPost = {
//     id: number;
// }

Omit

指定した key を除外する

type Post = {
  id: number;
  title: string;
  content: string;
};

type OmitPost = Omit<Post, 'id' | 'title'>;
// type OmitPost = {
//     content: string;
// }

Exclude

union 型から除外する

type RGB = 'red' | 'green' | 'blue';
type RG = Exclude<RGB, 'blue'>;
// type RG = "red" | "green"

ReturnType

関数の戻り値の型を取得できる

const add = (a: number, b: number): number => {
  return a + b;
};
type AddReturnType = ReturnType<typeof add>;
// type AddReturnType = number

他にも色々あります。
utility-types

as const

as constを宣言すると、それ以降は型推論で型が拡大せず、readonly になる。

let constPost = { id: 1, title: 'title', content: 'content' } as const;
// let constPost: {
//     readonly id: 1;
//     readonly title: "title";
//     readonly content: "content";
// }

enum は使うべきでないという話

よく言われているが、enum は非推奨
さようなら、TypeScript enum
TypeScript の enum を使わないほうがいい理由を、Tree-shaking の観点で紹介します

enum の代替としては次の感じ

const Country = {
  JAPAN: 'JAPAN',
  USA: 'USA',
  CHINA: 'CHINA',
} as const;

type Country = typeof Country[keyof typeof Country];

アサーション

null でないことが確信できる場合は、明示的に null でないことを指定できる

document.getElementById("test").innerHTML
// Object is possibly 'null'.
document.getElementById("test")!.innerHTML
// コンパイルエラーなし

let text;
if (...) {
  text = 'text1'
} else if (...) {
  text = 'text2'
}
text.substr(...)
// let text: string | undefined
// Object is possibly 'undefined'
text!.substr(...)
//コンパイルエラーなし

基本的には非推奨なので、?を使っておくのが無難

document.getElementById('test')?.innerHTML;

Template Literal Types

4.1 の目玉機能 Template Literal Types

template literal が型定義で使用できる。
一応 template literal を説明すると文字列の中に変数を埋め込める文法

const world = 'World';

console.log(`Hello ${world}!`);
// 'Hello World!';
type DataFormat = 'HTML' | 'JSON' | 'XML';
type Parser = `parse${DataFormat}`;
// type Parser = "parseHTML" | "parseJSON" | "parseXML"
type parser1: Parser = 'parseHTML'
type parser2: Parser = 'parseHogehoge'
// Type '"parseHogehoge"' is not assignable to type '"parseHTML" | "parseJSON" | "parseXML"'.

type Satoshi = `Satoshi${string}`;
const satoshi1: Satoshi = 'Satoshi';
const satoshi2: Satoshi = 'Satoshi Tanaka';
const notSatoshi: Satoshi = 'Taro Tanaka';
// Type '"Taro Tanaka"' is not assignable to type '`Satoshi${string}`'.

Template Literal Types があれば、もはや SQL にも型をつけることができる
ts-sql

最後に

TypeScript は柔軟に型が定義できて楽しいですが、普段から意識して使っていかないと勘が鈍るので積極的に使っていきたいですね。

Discussion

iemfafijeoijaiemfafijeoija

よく言われてると書いてるが、参考リンクは2つしかないですよね。
本当に非推奨ならtypescript公式等でそう名言されてるはずです。
treeshakingの問題に触れてますが、そもそも使わないenumを定義している時点でおかしいのではと疑問がある。他の言語でもenumは常識的に使用されているし、少数の意見で断定する言い方はよくないのでは?