TypeScriptで最低一つは必須なオプションオブジェクトの型を作る

JavaScriptにおいては、関数等がさまざまなオプションを受け取る場合にそれらをオブジェクトとして受け取ることがよく行われます。例えばよく見る所ではDOMの[addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)などが例として挙げられます。

```js:addEventListenerの例
document.addEventListener('touchend', handler, {
  once: false,
  passive: true,
});
```

この例では第3引数でオプションたちをオブジェクトにまとめて渡しています。
オプションたちは往々にして省略可能です。

さて、TypeScriptでもこのようなオブジェクトに型をつけることは容易です。TypeScriptにはオブジェクトの省略可能なプロパティを意味する`?`が備わっているからです。

```ts
interface Options {
  capture?: boolean;
  once?: boolean;
  passive?: boolean;
}
```

この例では、`Options`型の3つのプロパティは全て省略可能です。そのため、`{capture: false, once: true, passive: false}`や`{passive: true}`、あるいは`{}`などは全て`Options`型がつきます。

この記事ではもう少し複雑な場合を考えます。オプションのうち、「どれか一つは必須でほかは省略可能」という場合を考えてみましょう。上の例では、`{capture: true}`や`{once: true, passive: false}`などは許すが`{}`は許さないということになります。この記事では、このような型を作ることを目標とします。

## union型を使えば楽勝？
まあ、これは難しい話ではありません。簡単な例として、`foo`と`bar`という2つの省略可能なオプションがある場合を考えます。この場合、union型を用いて次のようにすれば解決です。

```ts
type Options =
  | {
    foo: string;
    bar?: number;
  }
  | {
    foo?: string;
    bar: number;
  }
````

すなわち、`foo`が指定された場合と`bar`が指定された場合の両方の型を用意してそれらのunionをとればいいのです。

しかし、見ればわかるとおりこれは明らかに理想的な解決策ではありません。`foo`の型や`bar`の型が2回書かれています。
理想的には、以下のような1つの型定義から上のunion型を生成したいですよね。

```ts
interface Options {
  foo?: string;
  bar?: number;
}
```

そこで、TypeScriptの機能を駆使してこれを実現するのがこの記事の内容です。
以下の内容が何を言っているのか分からない場合、筆者による以下の記事が参考になるかもしれません。
逆に、以下の記事の内容を用いて自分で解決策を作ることができれば初級～中級程度のTypeScript力があると言えると思います。

- [TypeScriptの型入門](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a)
- [TypeScriptの型初級](https://qiita.com/uhyo/items/da21e2b3c10c8a03952f)

## TypeScript的解決策
では、早速ですが答えを見ましょう。

```ts
type RequireOne<T, K extends keyof T = keyof T> =
  K extends keyof T ? PartialRequire<T, K> : never;
type PartialRequire<O, K extends keyof O> = {
    [P in K]-?: O[P]
} & O;
```

ここで定義した`RequireOne`が今回欲しかったものです。これは以下のように使います。

```ts:使い方
type Options = RequireOne<{
    foo?: number;
    bar?: number;
    baz?: string;
}>

const opt1: Options = {foo: 3};
const opt2: Options = {bar: 5, baz: "foo"};
const opt3: Options = {foo: 123, bar: 123, baz: "foo"};
// エラー
const opt4: Options = {};

/** オプションを受け取る関数の例 */
function useOptions(opts: Options) {
    const {
        foo = 0,   // number型
        bar = 100, // number型
        baz,       // string | undefined型
    } = opts;
}
```

`opt1`や`opt2`を見て分かるように、どのオプションも省略可能となっています。ただし、`opt4`のように全部省略されているとエラーとなります。


## 解説
まず`PartialRequire`を解説します。

```ts
type PartialRequire<O, K extends keyof O> = {
    [P in K]-?: O[P]
} & O;
```

これは、オブジェクトの型のうち一部のみを省略不可とした型を返すものです。
どのフィールドを省略可能とするかを`K`で指定します。
例えば、上の例の`Options`に対して`PartialRequire<Options, 'foo'>`とした場合は`foo`のみが省略不可能になった型、すなわち`{foo: number; bar?: number; baz?: string}`（と同等の型）が返されます。

定義を見ると、`&`の前が省略不可能部分の定義となっており、`-?`で省略可能性を取り除いているのがポイントです。`PartialRequire<Options, 'foo'>`の場合はここが`{foo: number}`となります。それに`O`をそのままくっつけることで残りの部分を維持しています。

これを用いると、`Options`から生成すべき型は`PartialRequire<Options, 'foo'> | PartialRequire<Options, 'bar'> | PartialRequire<Options, 'baz'>`となります。
これを作るのが`RequireOne`です。

```ts
type RequireOne<T, K extends keyof T = keyof T> =
  K extends keyof T ? PartialRequire<T, K> : never;
```

これは`RequireOne<Options>`として使うとまず型引数`K`がデフォルト値の`keyof T`となります。今回の場合は`keyof Options`なので`'foo' | 'bar' | 'baz'`というunion型になります。
こうなると、union型の各要素を`'foo'` → `'PartialRequire<Options, 'foo'>`のように変換すれば目的が達成できそうですね。
これは実は、条件型のunion distributionという性質を使えばまさにやりたいことがそのまま実化できます。`K extends ***`という形の条件型を書くことで、`K`の中のunionが分解されて`PartialRequire<T, *>`のunionに変換されます。

---

なお、`RequireOne`の型引数を2つにしているのは、`keyof T`を型変数に入れることでunion distributionを発生させるという目的が一つありますが、2つ目の型引数明示的に指定することでいずれか一つを必須にする範囲を狭める機能をつけるという副次的効果もあります。例えば、`Options`の`foo`プロパティと`bar`プロパティのどちらか1つを指定しなければいけない場合は`RequireOne<Options, 'foo' | 'bar'>`と書けます。

## まとめ
今回は、比較的実用に近そうな例を用いてTypeScriptの条件型の使用例を示しました。

TypeScriptは、このようにオブジェクトの型をいじるのが結構得意です。
皆さんもぜひ妥協せずにTypeScriptで型をつけていきましょう。