useForm の型引数について調べてみた！

:::note
私の個人的な見解であり、所属する団体とは一切関係ありません。また、可能な限り検証は行っていますが、誤りがある可能性もあります。その際は、ご指摘いただけると嬉しいです！
:::

# はじめに

react-hook-form を使ってフォームを追加して遊んでおり、ある用途のパラメータがわからないので react-hook-form を解析してるうちに型引数の沼にハマり、脱した（？）ので備忘録として残します。


# 出会いは突然に

`useForm` を調べていくうちに `TFieldValues extends FieldValues = FieldValues` と出会いました。~~そこから泥沼です~~

```react
export function useForm<
  TFieldValues extends FieldValues = FieldValues,
  TContext = any,
  TTransformedValues extends FieldValues | undefined = undefined,
>(
  props: UseFormProps<TFieldValues, TContext> = {},
)
```

URL: https://github.com/react-hook-form/react-hook-form/blob/29ae59660995c4c0728aac26dd9051fc617a1548/src/useForm.ts#L46-L52

`TFieldValues` は `FieldValues` を継承して、なおかつ `FieldValues` と等しい？それとも `TFieldValues` は `FieldValues = FieldValues` を継承している？と**誤った理解**をしていました。

# 紐解いていく

わかりやすそうなところから紐解いていく

## FieldValues お前はだれだ！

```react
export type FieldValues = Record<string, any>;
```

URL: https://github.com/react-hook-form/react-hook-form/blob/29ae59660995c4c0728aac26dd9051fc617a1548/src/types/fields.ts#L26

`Record<Keys, Type>` は

> Record<Keys, Type>はプロパティのキーがKeysであり、プロパティの値がTypeであるオブジェクトの型を作るユーティリティ型です。

となっています。

引用：https://typescriptbook.jp/reference/type-reuse/utility-types/record

つまり、`Record<string, any>` だと次のようなオブジェクトは型通りです。

```typescript
{
  'name': 'Jiro YAMADA',
  'age': 56
}
```

## `TFieldValues extends FieldValues = FieldValues` は何だ！

まず `TFieldValues extends FieldValues = FieldValues` は `TFieldValues extends FieldValues` と `= FieldValues` に分けて理解します。

### `TFieldValues extends FieldValues` は何だ！

`TFieldValues` は `FieldValues` を継承しています。型引数のコンテキストでオブジェクトを対象とした場合にオブジェクト指向で慣れ親しんだ継承とは違うようです。

次のサンプルをみて確認すると、Field 型は value プロパティを持つ型です。このコンテキストでいう継承は value プロパティを含むオブジェクトであれば OK ということです。また、サンプルでは型を省略して実行していますが、これは型引数の制約から推論しています。

```typescript
type Field = {
  value: string
};

function submitForm<T extends Field>(fields: T[]) {
  fields.forEach((field, _) => {
    console.log(field)
  });  
}

// 実行できる
submitForm([
  {
    value: 'Taro'
  },
  {
    value: 'YAMADA'
  },
])

// 実行できる
submitForm([
  {
    value: 'Taro',
    hiddenValue: 'taro'
  },
  {
    value: 'YAMADA'
  },
])


// 実行できない
submitForm([
  {
    checkboxValue: 'Taro' // checkboxValue はない
  },
  {
    value: 'YAMADA'
  },
])
```

## `= FieldValues` は何だ！

これは型引数のデフォルトになります。`Field` に型を指定しない場合は `number` になります。

```typescript
type Field<T = number> = {
  value: T;
};

// 'banana' は number ではないので型チェックでエラー！
const field0: Field = {
  value: 'banana',
}

const field1: Field<string> = {
  value: "",
};

const field2: Field<"checkbox"> = {
  value: "checkbox",
};
```

## 合わせると・・・？

`TFieldValues extends FieldValues = FieldValues` はなにをしてるかというと

1. 型チェックをパスできるのは `FieldValues` を継承したもの
1. 型を省略した場合は型引数の制約で FieldValues を継承したものを期待
1. 型を省略した場合はデフォルト型引数の `FieldValues` を継承したものを期待

をしています。

あれ？省略したときの振る舞いが同じですね。そうここがわからないポイントなんです。

:::note warn
勘違いしてる場合があります。ご注意ください。
:::

react hook form でも同じような記述はありますし次のプログラムを書いてみたのですが、すべてのプロパティをすべて必須にしている（つもり）なのですが、一部のプロパティだけでも型チェックはパスします。

```typescript
type Field = {
  value: string
};

type HiddenField = {
  value: string,
  hiddenValue: string
};

type RequiredHiddenField = Required<HiddenField>

function submitForm<T = RequiredHiddenField>(fields: T[]) {
  fields.forEach((field, _) => {
    console.log(field)
  });  
}

// 実行できる
submitForm([
  {
    value: 'Taro'
  },
  {
    value: 'YAMADA'
  },
])
```

もちろん次のように extends を使って継承すれば期待通り、すべてのプロパティがあるオブジェクトを期待します。

```typescript
function submitForm<T extends RequiredHiddenField>(fields: T[])
```


# 終わりに

型むずかしい！