TypeScript + React で作る checkbox の checked と defaultChecked を型で「排他」にしてエラー検知しやすくした話

## checkbox を React で書いたことありますか？
ちょっとサンプルを書いてみたので、ご覧ください。

```tsx:checkbox.tsx
import React, { FC } from 'react';

interface Props {
  checked?: boolean;
}

const Checkbox: FC<Props> = ({ checked = false }) => (
  <input type="checkbox" checked={checked} />
);

export default Checkbox;
```

超最低限で書くと、こんな感じになります！
`checked` という Props を用意して、それを `<input>` に流しています。

流れてくる `boolean` によって、 checkbox の check 状態を制御できるようになっているわけです。

外部から `<Checkbox checked={true} /> ` とすれば「check された状態」になりますし、
`<Checkbox checked={false} />` とすれば「check されていない状態」になります。

つまり、これはどういうことかというと、、、

**どんなに checkbox をクリックされても `boolean` の値は変更されないため、`true` であれば永遠に check された状態になるということです。**

ここが、 **React の フォーム系 UI を実装するときの混乱するポイントの一つです。。。**

## `checked` と `defaultChecked`
checkbox には checked に影響する Props が2つ存在します。

`checked` というのは、 チェックされているか否かの状態が `boolean` 値によって常にコントロールされているときに使う Props です。つまり先ほどの例のような状態です。
値が固定されていたら、どんなにユーザが操作しても値が変わらない状態です。
このコンポーネントのことを、制御されている状態のコンポーネントということで、 **Controlled Components** と呼びます。

一方、`defaultChecked` は、従来の HTML のようにユーザ操作によって、チェックされている状態を変更できる状態にしておく Props です。
一見、`defaultChecked`の方が扱いやすいイメージがありますが、 `defaultChecked` だと初回の render 時に流された `boolean` の値で UI が決定したあと、 `defaultChecked` に値を流し直しても UI は変更されません。
なので、 Controlled Components に対して、こちらは **Uncontrolled Components** と呼びます。

どちらを使ってももちろん良いですし、その時折でどちらを使うかを判断する必要があるとは思いますが、、、

**少なくとも、両方を同時に使うことはありません！**

「コントロールしないのにコントロールする」ってもう破綻していますもんね。

`checked` と `defaultChecked` を同時に指定すると、 React も Warning を吐いてくれます。

```
Warning: Checkbox contains an input of type checkbox with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://fb.me/react-controlled-components
```

いっぱい英語で書かれていますけど、 `but not both` と書かれているので、今回のことを言っている Warning で間違いないでしょう！

（より詳しく Uncontrolled Components について知りたい場合は以下を参考にしてください。）
[Uncontrolled Components – React](https://reactjs.org/docs/uncontrolled-components.html)

## ならば、片方しか指定できないようにしたい！
やっと本題です！笑

`Checkbox` というコンポーネントを使いたいときに、 `checked` と `defaultChecked` の**両方の props を流そうとしたら「型エラー」が出るように型で縛っちゃえばいいと思いませんか？**

ひょっとしたら、これは人類史上最大の発明をしてしまったかもしれない。。。

`checked` と `defaultChecked` という2つの Props がある時点で混乱するのに、これを両方指定すると Warning が出てしまうとなったら、もう混乱に混乱を重ねてしまって大変な騒ぎです！！！

つまり、
- `<Checkbox checked={true} />` は **OK**
- `<Checkbox defaultChecked={true}>` は **OK**
- `<Checkbox checked={true} defaultChecked={true} />`は **NG**

となればいいわけです。

タイトルの「排他」は少し語弊がありますね。
正確に言うと、**[「否定論理積」](https://ja.wikipedia.org/wiki/%E5%90%A6%E5%AE%9A%E8%AB%96%E7%90%86%E7%A9%8D)**の振る舞いをさせたいということです。

そんなことできんのか！？という感じですが、 **`never` 型** というのを使えばいけちゃうそうです。

## `never` を使って checkbox の型を正しく縛ろう！
まずは、`never` は使わずに Checkbox Component を作ってみます。

```tsx:checkbox.tsx
import React, { ReactNode, FC, ChangeEvent } from 'react';

interface Props {
  checked?: boolean;
  defaultChecked?: boolean;
  onChange?(event: ChangeEvent): void;
  chidlren?: ReactNode;
}

const Checkbox: FC<Props> = ({
  checked,
  defaultChecked,
  onChange = () => {},
  children
}) => (
  <label className="checkbox">
    <input
      type="checkbox"
      checked={checked}
      defaultChecked={defaultChecked}
      onChange={event => onChange(event)}
      className="checkbox__main"
    />
    <span className="checkbod__label">{children}</span>
  </label>
);

export default Checkbox;
```

ちゃんと `onChange` と `children` も渡せるようにしてみました。
こうしておけば、 `onChange` で `checked` の boolean も変えやすくなりますし、 checkbox の横に文字を入れることもできます。

ただ、この状態で `Checkbox` を使うと、、、

```tsx
const App => () => <Checkbox checked={true} defaultChecked={true} />
```

利用する側は、上記のように `checked` と `defaultChecked` のどちらを入れてもなんのエラーもなくコンパイルできてしまいます。

これを never で防いでいきます。

### `never` を入れた型を作っていく
詳しくは、[こちらの記事](https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a#never%E5%9E%8B)を読んでいただければと思いますが、 `never` という型があり、その記事から引用すると
> 属する値が存在しない型

とのことです。「無が有る」みたいですね。難しい。。。

難しいのですが、この `never` を使うと、そこに何かしらの値が入ると型エラーが起こります。
なんでもです。`null` も `undefined` もエラーです。
この特性をうまく活用していく形になります。

まずは、 Controlled Components でも Uncontrolled Components でも両方で使う Props の型を定義します。

```tsx
interface BaseProps {
  onChange?(event: ChangeEvent): void;
  chidlren?: ReactNode;
}
```

そして、 Controlled Components 用の Props の型と、 Uncontrolled Components 用の型を用意していきます。
両方とも、共通で使う `BaseProps` を extends させておきます。

```tsx
interface ControlledProps extends BaseProps {
  checked?: boolean;
  defaultChecked?: never;
}

interface UncontrolledProps extends BaseProps {
  checked?: never;
  defaultChecked?: boolean;
}
```

ここで `never` 型が出てきました。

`ControlledProps` では `defaultChecked` が `never` なので、 **`defaultChecked` に何かしら指定すると怒られます**。
`defaultChecked: never` にすると、**指定しなかったとしても怒られてしまう**のですが、undefined を許容してそれを回避しています。

`UncontrolledProps` は、`ControlledProps`の逆を書いておきます。

そして、この2つの Props を、、、

```tsx
type Props = ControlledProps | UncontrolledProps;
```

**Union 型にしてしまいます！！！**

Union 型とは、`string | number` の場合、どちらでも良いよ〜という型になるということです。
つまり、`Props` という型は、`ControlledProps` でも `UncontrolledProps` でもどちらでも良いよ〜という型になったということです。

すべてのコードをまとめると、以下のようになります。

```tsx:checkbox.tsx
import React, { ReactNode, FC, ChangeEvent } from 'react';

interface BaseProps {
  onChange?(event: ChangeEvent): void;
  chidlren?: ReactNode;
}

interface ControlledProps extends BaseProps {
  checked?: boolean;
  defaultChecked?: never;
}

interface UncontrolledProps extends BaseProps {
  checked?: never;
  defaultChecked?: boolean;
}

type Props = ControlledProps | UncontrolledProps;

const Checkbox: FC<Props> = ({
  checked,
  defaultChecked,
  onChange = () => {},
  children
}) => (
  <label className="checkbox">
    <input
      type="checkbox"
      checked={checked}
      defaultChecked={defaultChecked}
      onChange={event => onChange(event)}
      className="checkbox__main"
    />
    <span className="checkbod__label">{children}</span>
  </label>
);

export default Checkbox;
```

Union 型として定義した `Props` は Checkbox Component の Props として型定義してあげます。

こうしてあげると、なんと「否定論理積」のような振る舞いをしてくれます！！

### 実際に使ってみる
というわけで、実際に import して、 `checked` と `defaultChecked` の2つを指定してみましょう。

```tsx
const App = () => (
  <Checkbox checked={true} defaultChecked={true} />
);
```

こう書いてみると、、、

```
Type '{ checked: true; defaultChecked: true; }' is not assignable to type '(IntrinsicAttributes & ControlledProps & { children?: ReactNode; }) | (IntrinsicAttributes & UncontrolledProps & { children?: ReactNode; })'.
  Type '{ checked: true; defaultChecked: true; }' is not assignable to type 'UncontrolledProps'.
    Types of property 'checked' are incompatible.
      Type 'true' is not assignable to type 'undefined'.
```

というエラーが出ました！！！

エラーの中身は複雑になっちゃっていますが、両方指定するとちゃんとエラーが出ることは確認できました！！！

取り急ぎ、やりたいことはできました！

以下のときにはちゃんとエラーは出ないので、大成功というわけですね！

```tsx
const App = () => (
  <>
    <Checkbox />
    <Checkbox checked={true} />
    <Checkbox defaultChecked={true} />
  </>
);
```

## まとめとか解説とか謝辞とか
もともとこの記事は Type Guard の記事として書こうとしていたのですが、試しているうちに、`ControlledProps` と `UncontrolledProps` を Union 型にするだけで今回のやりたいことが満たせてしまったので大変びっくりしました。。笑

最初、記事を書く前にテストで書いていたコードには、

```tsx
function checkControlledProps(props: Props): props is ControlledProps {
  return typeof props.checked === "boolean";
}
```

という関数が書いてあって、**「 checked に `boolean` がセットされていれば `ControlledProps` とみなしますよ！という関数を書いて、今回の問題を解決します！」**という記事にする予定でしたw

ただ、 

```tsx
type Props = ControlledProps | UncontrolledProps;
```

と書いているときに、これってどういう型なんだ？と思い始めて、思い切って Type Guard の関数を削除してみたら、結局同じエラーを吐いたので急遽 Type Guard には言及しない記事にしましたw

Type Guard に関しては以下の記事が大変わかりやすいので、ぜひ読んでみてください！笑
[TypeScript の Type Guard を使ってキャストいらず](https://qiita.com/propella/items/33433278497f290ceadb)

そして、肝心の、どうして今回うまくできたのか、についてですが、
おそらく Union 型にしたことによって、 `checked` に `boolean` を流せば自動的に `ControlledProps` が採用されて、 `defaultChecked` に `never | undefined` が採用されたんだろうなと思いました。
わざわざ関数を作って判別しなくても、Union 型のどちらが採用されるべきかは明確になるので、今回のやりたかったことができたのだろうなと思っています。

最後、記事を書いておきながら、あれ？となってしまったので、なにかご指摘があればコメント欄にご記載くださいw

また、今回のこの記事は私一人の力で生み出したわけではなく、以前会社で助っ人としてアサインされた方に教えてもらったものを参考に書いております！
教えてもらえて超感謝しています！

以上です！