3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TypeScriptの型定義でmapっぽいことをする方法

Last updated at Posted at 2020-06-24

概要

Objの中身をそれぞれ分解したObj2を作るにはどうすれば良いでしょうか。

type Obj = {
  foo: string;
  bar: number;
  baz: boolean;
};

type Obj2 = { foo: string } | { bar: number } | { baz: boolean };

これが型の外の世界であれば、多くの方はmapを使うかと思います。 

const obj = {
   foo: 'string',
   bar: 100,
   baz: false
};

const obj2 = Object.keys(obj).map(key => ({ [key]: obj[key] }));
// obj2 = [{ foo: 'string' }, { bar: 100 }, { baz: false }];

この記事では、このmapっぽいことを型の世界で行う方法について説明して行きます。

やり方

TypeScriptにおけるin

TypeScriptにおけるintruefalseを返すin演算子として説明されることが多いですが、もう1つの意味があります。
それはMapped typesです。

ref) Mapped types

これはまさに配列に対してmapをかけるような処理となります。

const animals = ['dog', 'cat'];
const animalObject = animals.map(v => () => v);
// animalObject = {
//    dog: () => 'dog',
//    cat: () => 'cat',
// }

type Animal = 'dog' | 'cat';
type AnimalObject = { [V in Animal]: () => V };
// AnimalObject = {
//    dog: () => 'dog',
//    cat: () => 'cat',
// }

keyofとMapped types組み合わせる

このMapped typesは、keyofと組み合わせることでオブジェクトの型それぞれに対して特定の処理を噛ませることができるようになります。

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

type UserProps = keyof User;
// UserProps = 'id' | 'age' | 'name'

type UserReadonly = { readonly [P in UserProps]: User[P] };
// type UserReadonly = {
//   readonly id: string;
//   readonly age: number;
//   readonly name: string;
// }

これはUtility TypesのReadonly<T>がやっていることと同じですね。
Partial<T>Nullable<T>など多くのUtility Typesがinkeyofを組み合わせて使っているように、これは覚えておくと非常に便利な書き方です。

結合した後バラす

さて、|で繋げられたそれぞれの型に対して特定の操作をしたいだけの場合はどうすれば良いでしょうか。
Mapped Typesを使うとそれぞれの型に対して操作ができます。
しかし結果は、1つのobjectとして結合されてしまいます。

type Animal = 'dog' | 'cat';
type AnimalObject = { [V in Animal]: () => V };
// AnimalObject = {
//    dog: () => 'dog',
//    cat: () => 'cat',
// }

そのため、Mapped Typesによって生成された結果を一段回掘ることで、元の次元に戻すことができます。

type AnimalFunctions = AnimalObject[Animal];
// type AnimalFunctions = () => 'dog' | () => 'cat';

これらを応用することで、様々な操作が可能となります。

回答

というわけで概要の問題については下記のように定義することができます。

type Obj = {
  foo: string;
  bar: number;
  baz: boolean;
}

type Obj2 = { [T in keyof Obj]: Record<T, Obj[T]> }[keyof Obj]
// Obj2 = { foo: string } | { bar: number } | { baz: boolean }

社内Slackで質問があったので、記事にしてみました。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?