React Hook Formのアップデート内容 - Version 7


(❤️ ブログ記事を翻訳してくれた日本のチーム、KotaroKeiyaMarinaに感謝します。)

React Hook Formもそろそろ2年目を迎えようとしています。プロジェクト自体は、初日と同じように今でも活発に活動しています。この数年の間に、バージョン7の作成やデザインに繋がる多くの学びや経験がありました。私はここで、次のメジャーバージョンでの改善点とその理由をいくつか紹介します。フォームを構築するという点で、React開発者がより楽に開発できるようにする私たちの使命に変わりはありません。

V7 visions:


📖 (DX)厳密に型付けされたフォーム
🏋🏻‍♀️ シンプルさと一貫性
🏎 パフォーマンス
💁‍♂️ パッケージサイズの削減


このビジョンを説明するために、APIの変更点を見ていきましょう。

</> register

主な変更点の一つは register API です。ほとんどの開発者が知っているように、React Hook Form は TypeScript を使って構築されており、ほとんどの TypeScript 開発者にとって、型安全性があることは重要な側面です。私たちが直面していた課題の一つに、input name を型チェックすることがありましたが、 TypeScript 4.1のアップデートTemplate Literal Typesで可能になりました。このアップデートには本当に興奮して、React Hook Formで使えるようにすぐに取り掛かり始めました。

nameを型安全にするには以下のように変更する必要があります。

- <input name="test" ref={register} />;
+ <input {...register("test")} />;

// 以下のpropsがinputに分散されます。
const { onChange, onBlur, ref, name } = register("test");

このように、register 関数を呼び出すことで型チェックができるようになりました。元のAPIには賛否両論ありますし、型安全性と相性が悪いのは間違いありません。この変更によって、 useController, Controller などのAPIでも型チェックも行ってくれるようになります。

次の画像は、入れ子になったフィールドの入力名に対する型チェックの様子を示しています。

β版をインストールした状態で、以下のコードサンドボックスで試すことができます。

</> resolver

スキーマを使ってバリデーションしている開発者のために、resolverがパワーアップしています。resolverはバリデーションの状況が不足していました。つまり、どのinputsがバリデーションされたかの情報を取得することができていませんでした。私たちはこの部分を改善しており、開発者はどのフィールドが検証されたかにアクセスできるようになり、そのrefも同様に行うことができるようになります。これにより、以下のような機能が利用できるようになります。

  • バリデーションの最適化: スキーマバリデーションライブラリがフィールドレベルのバリデーションをサポートしている場合、開発者は onChange の度にスキーマ全体を実行せずにバリデーションを最適化することができます。

  • フォーカス管理: より良いカスタムフォーカス管理のための、input DOM 要素へのアクセス。

以下に Resolver の型定義を示します:

- resolver: (values: any, context?: object) => Promise<ResolverResult> | ResolverResult
+ resolver: (
+   values: any, 
+   context: object | undefined, 
+   options: { 
+     criteriaMode?: 'firstError' | 'all', 
+     names?: string[],
+     fields: { [name]: field } // Support nested field
+   }
+ ) => Promise<ResolverResult> | ResolverResult 

Joris はこれらの素晴らしい仕事の裏側にいる人です。今後、resolver について詳しい技術記事を書いてくれるでしょう。

</> useFormState

現在のフックフォームのバージョンでは、フォームの状態の更新は useForm を呼び出したルート/フォームレベルで再レンダリングされます。バージョン7ではこれが変更され、新たに導入されたカスタムフック useFormState を使うと、コンポーネントレベルで個々のフォーム状態の更新を監視できるようになります。これによって、フォームの状態の更新がコンポーネントに通知されるタイミングを開発者側でコントロールできるようになります。また、useControllerControler の中に useFormState を組み込んでいるので、制御された各inputは再レンダリングの観点から完全に分離されます。

// Subscribe to isDirty, touchedFields at the component level
const { isDirty, touchedFields } = useFormState();

// Subscribe **only** to isDirty and isTouched.
const Test = () => {
  const { meta: { isDirty, isTouched } } = useController();
}

ここでは、この新しいカスタムフックを利用して、再レンダリングがコンポーネントレベルで分離されているDevToolの良い例を紹介します。

</> useForm

初期バージョンの当初から、HTMLの標準とネイティブフォームの挙動に合わせたものにしようとしています。その例として、inputが削除されるとその入力値も削除されます。しかし、このような動作は、多くのReact開発者に、なぜアンマウント後に入力値が消えてしまうのかということに戸惑いを与えていました。その戸惑いを解消するために、shouldUnregisterという設定を用意して、入力値が消えないようにするかユーザー側で選択できるようにしていました。その結果、コード内に多くの余分なロジックと複雑さをもたらすことになっていました。バージョン7では shouldUnregister を廃止し、開発者は unregister で入力値の削除を制御しなければならなくなります。この変更には賛否両論ありますが、ほとんどのReact開発者にとっては、この変更はより意味のあるものになると思います。以下に例を示します。

const [show, setShow] = React.useState(true);

// V6 the following input value will be removed
{show && <input ref={register} name="test"} 

// V7 the following input value will be retained
{show && <input {...register('test')}} // V7

// V7 until the following line is invoked then the value will be removed
unregister('test')

また、この変更はライブラリのバンドルサイズの変更にも大いに役立ちました。


結論

React Hook Form は、一般的にはより良い型チェックを持つようになり、より小さく、より速く、より開発者に優しいものになります。この投稿で、私たちがバージョン 7 に盛り込もうとしている改善点とその根拠を理解していただけると幸いです。もし何かフィードバック、懸念、質問があれば、いつでも私たちの バージョン 7 RFC に参加してください。

V7についてYouTubeに投稿したこちらのshort talkも見ることができます(17分)。

おまけに

また、以下のような改善を行いました。

Documentation:

DevTool:

Thanks

このメジャーバージョンに関わってくださった皆様、貢献してくださった皆様に感謝します。

  • 日本チームの皆さん、ありがとうございました。Kotaro, Keiya と日本の開発者コミュニティに感謝します。

  • Jorisさんは、resolverの次のバージョンを作ってくれた人で、残りの人はhook form teamさんです。

  • V7の議論に一貫して関わり、バグや問題を一人で報告してくれた wdfinch

応援&協賛ありがとうございます