※アソビュー! Advent Calendar 2024 (B面) の 10 日目の記事です。
アソビューで新規事業のプロダクト開発を行っている竹内です。 私はこれまで主にバックエンドとプロジェクトマネジメントに携わってきました。今回初めて React + TypeScript に挑戦したので、今回やったことを自身のためにも整理しつつ、ポイントだと感じたところを書いていきたいと思います。
はじめに
まず私がどれくらいのフロントエンド歴かといいますと・・・
- SI でバックエンド出身、最近までPjM中心
- 久しぶりにプレイングマネージャーでコードを書くことに
- フロントエンドをやったのは JQuery+bootStrap のあたりまで。。
- Angular や React, Vue が出た頃にそれぞれ の Getting Started をやった程度
といった感じです。
で、今回は社内向け管理機能の新規画面を担当したのですが、弊社フロントエンド・テックリードから、
React + TypeScript + mantine + React Hook Form でやってください!(以上)
というお言葉と参考になるコードを教えていただき、初めて触れる技術が多いので楽しみ半分不安半分で作業に取り掛かりました。
学んだ技術
前提
新規といっても React プロジェクトは既に整っている状態なので、以下のような共通部分は今回割愛します。
- 既存の React プロジェクトに画面を追加するので、ルーティングとか認証周り
- ビルドや Lint , CIなど
それと、今回 IDE は IntelliJ で行いました。社内でもフロントエンドは VSCode を使うのが主流だと思いますが、IntelliJ は私が比較的使い慣れているためです。IntelliJ の プラグイン追加は特に行っていません。といっても、TypeScript や Lint の警告はエディター上で表現してくれるので大きな問題はありませんでした。
どんなふうに進めていったか
今回初めて触れる技術も多かったのですが、主に以下のことをやっていたと思います。
- 参考になるコードを読む
- 公式ドキュメントを読む
- 小さなところから一つずつ書いて動かす
特に 3 つめはやはり大事なことだなと改めて感じました。意図したように動かせたときは嬉しいものですね。
作成した画面
今回 2 画面作成しました。
- とある設定を登録する入力フォーム。一部の入力フィールド(Role と Content)をボタンで動的に増減可能
- とあるデータをインポートする画面
学んだ技術
今回新たに学んだ技術を、自身の整理のためにも技術ごとに少しまとめたいと思います。まだ理解が浅いので誤認識があるかもしれません。その点はご了承ください。
React
コンポーネント単位で UI を構築しやすくするためのライブラリ。 とりあえずは状態と hooks がある程度理解できればなんとかなるんではないかという印象。
TypeScript
JavaScript に静的型システムなどの機能が拡張された言語。IDE やライブラリが TypeScript に対応しているので、随所にありがたみを感じる。もはやこれがない世界は想像できないのではないか。
yup
TypeScript の型定義と連携してフォームのデータ構造とバリデーションを管理できるライブラリ。これがあるおかげでフォームのデータ構造定義のみならず、バックエンドへのリクエストの型定義、フォームのバリデーションを宣言的に以下のような感じで書ける。便利すぎる!
const schema = yup.object().shape({ code: yup.string().required('Code は必須です。'), messages: yup .array() .of( yup.object().shape({ role: yup.string().required('Role は必須です。'), content: yup.string().required('Content は必須です。'), }), ) .min(1, 'メッセージは1つ以上設定してください。'), })
React Hook Form(RHF)
https://react-hook-form.com/
yup で定義した型を使ってフォームに必要な設定や関数群を提供してくれる。便利すぎる!!!
// 事前に yup による schema 定義がある type FromSchema = yup.InferType<typeof schema> //...中略... const { register, // mantine コンポーネントと紐つけるためにつかう control, // mantine コンポーネントと紐つけるためにつかう handleSubmit, // submit につかう formState: { errors }, // フォームの状態を知りたいときつかう } = useForm<FromSchema>({ // yup 型定義でのフォーム宣言 mode: 'onChange', // バリデーションは onChange のタイミングで resolver: yupResolver(schema), // yup定義の内容でバリデーションする })
useFieldArray
RHF で配列を使って入力フィールドを動的に増減させるためのライブラリ。「え、これだけでいいの?」というくらい簡単にできる。今回最も衝撃を受けたかもしれない。便利すぎる!!!
// 事前に yup による schema 定義と RHF の宣言がある const { fields, append, remove } = useFieldArray({ control, // RHF で取得できる control name: 'messages', // yup 定義内の増減させたい配列キー名 }) //...中略... {fields.map((field, index) => ( //...中略... <Textarea label={`Content ${index + 1}`} {...register(`messages.${index}.content`)} error={errors.messages?.[index]?.content?.message} /> //フィールド削除ボタン <Button onClick={() => remove(index)}>Delete</Button> ))} //...中略... // フィールド追加ボタン <Button onClick={() => append({content: ''})}>Add a message</Button>
mantine
React 対応 UI コンポーネントライブラリ。今回は基本的にこのライブラリの UI コンポーネントを使って組み立てていくだけ。CSS も特殊なことをしなければ基本書かなくて良いし、レスポンシブにもしてくれる。React Hook Form の関数を使うとフォームの入力コンポーネントとして挙動してくれる。公式サイトのロゴと色もかわいい。最高!
つまづいたところ
React コンポーネントの状態について
React コンポーネントが参照する値(状態)は単純な変数宣言ではなく useState などを使って宣言する必要がある認識です。なのですが、なんとなくふんわりとしかわかっていないせいか、少し変わったことをすると uncontrolled のエラーに引っかかりました。というのも、ある Textarea では値を文字列ではなく配列として持ちたかったのです。自分なりに考えて色々試しましたが uncontrolled のエラーが発生しうまくできませんでした。 yup では配列で定義しているのに Textarea の状態にするにはどうすれば良いんだ・・・
これを実現するためにいろいろな方法があると思いますが、こちらの記事を参考に今回このようにしました。
const schema = yup.object().shape({ codes: yup .array() .required() .min(1, 'Codes は1つ以上設定してください。') .of(yup.string().required()), }) type FromSchema = yup.InferType<typeof schema> //...中略... const { control, formState: { errors }, } = useForm<FromSchema>({ mode: 'onChange', resolver: yupResolver(schema), }) //...中略... <Controller name='codes' control={control} defaultValue={[]} render={({ field: { value, onChange } }) => ( <Textarea label='Codes (改行区切りで複数入力可能)' value={value?.join('\n')} // ここで配列から文字列に変換 error={errors.code?.message} onChange={(e) => onChange(e.target.value.split('\n'))} // ここで文字列から配列に変換 /> )} />
私の理解ではこうです。
- yup 定義、RHF では配列として定義
<Controller>
タグは配列として状態を持つ- Textarea のレンダリング、入力時(onChange)に 配列 <-> 文字列の変換を行う
- Textarea は配列ではなく文字列として扱う
これまで React は UI コンポーネントとその状態がある、ぐらいの理解でしたが、UI コンポーネント にはレンダリングがあるんだということに気づきました(あたりまえですが)。特殊なレンダリングをしたいとか、データを加工したい場合などにも今回の例で応用できるのかなと思いました。
hooks について
useState とか use なんとか の関数が hooks だよね?というざっくりした感じの理解でしかなく、今でも同じ程度です。今回は useState、 useEffect あたりを以下のシチュエーションで使いました。
- useState: コンポーネント内で状態を持ちたい!
- useEffect: レンダリングのあとに処理を実行したい!
hooks はかなり数があったり、React のライフサイクルも絡んでくるのできちんと理解しようとするにはそれなりに大変な印象です。はじめは、「こういうことをやりたかったらこのフックを使う!」といった感じで経験と知識を積み重ねるのがいいのかなと思いました。
チャレンジしてみての所感と今後の目標
yup + React Hook Form + mantine が便利すぎる!
これは便利すぎて感動しました・・。submit で API を呼び出す処理以外はほぼ書いていないんじゃないでしょうか。面倒なコードを書かなくていいのは本当に素晴らしいです。
React の理解が重要
今回これらのフロントエンドの技術にチャレンジしてみて思ったのは、前述のように便利なライブラリが多数あってとても良いのですが、やはり React の理解が重要だなと思いました。つまづいたところに記載したものも React コンポーネントの状態と hooks のことだったので、ベースとなる技術の理解ができるとよりスムーズに開発できるなと感じました。
まだやれていないところがある
今回やれていないなと思ったのは以下です。
- コンポーネント化・部品化
- CSS
- (既に実装済みだった) Routing とか認証とか
今回も冗長なコードがあったり可読性に難があったりしたので、コンポーネント化・部品化については早々に取り組もうと思っています。
最後に
今回 React + TypeScript にチャレンジしてみて感じたのは、意外とやれたぞ!ということでした。最初は結構時間かかるかもなーなどと思いましたが、便利なライブラリや参考になるコード、同僚のサポートに助けていただいて、ではありますが動くものができたということは自信につながりました。あとは、あとは新しい技術に触れるのはやはり楽しいものですね!
この記事が私のようなフロントエンド初学者の方の一助になっていたら嬉しいです!
PR
アソビューではより良いプロダクトを素早く世の中に届けられるよう、様々な挑戦を続けています。今回のように、初めてフロントエンドに挑戦する!といったことも実際にあったりします。 私達と一緒に働くエンジニアを募集していますので、興味のある方はぜひお気軽にエントリーください!(カジュアル面談もやってます!)