Upgrade to Pro — share decks privately, control downloads, hide ads and more …

10周年を迎えたZEN Study Webフロントエンドの次の10年へ向けた工夫

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

10周年を迎えたZEN Study Webフロントエンドの次の10年へ向けた工夫

TSKaigi 2026 スポンサーセッション
https://2026.tskaigi.org/talks/85

Avatar for tris

tris

May 25, 2026

More Decks by tris

Other Decks in Programming

Transcript

  1. 自己紹介 • ドワンゴ 教育事業本部 e-learningプロダクト開発部   ZEN Study Webフロントエンドセクション 小松(こまつ) • ここ2年ほど

    ZEN Study の開発に従事 • 趣味の1つ:  ドライブ【距離ガバ】  日本本土の海沿いを走破+α 2 ニコニコ超会議の 配信に映り込む 不審なスタッフ
  2. 本セッションのサマリー • ドワンゴ教育事業とZEN Studyの概要 ◦ 事業・サービス ◦ これまでの10年のごく簡単な振り返り • 次の10年に向けてメンテナンス性を向上するために行なっている取り組みの一

    部を、主にTypeScriptの観点から紹介 ◦ 外部とのやり取りの結果として、判別可能なユニオン型を活用し、状態を詳細にモ デリング (お昼休憩中なので、頭を使う難しい話ではなく、基本的な話をします) 3
  3. 10年の歴史がある = 10年の積み重ねがある • 最初の構築が急ピッチで行なわれた ◦ N高の開校と同時にリリースする必要があった ▪ 期限が決まっている中で、完全新規プロダクトの構築が行なわれた ◦

    開発スピードを優先した選択が行なわれた ▪ メンテナンス性の優先度は低かった • 複雑性が積み重なっている ◦ 新機能の追加 ◦ 対象となる学校の増加 ▪ S高・R高の開校、大学の開学 ◦ リファクタリングが追い付いていないコード 7
  4. 判別可能なユニオン型を利用する方法の概要 • 外部 API 呼び出しの結果は、成功/失敗のケースがある • それなら汎用的な Result<T, E> を使うのが良さそう

    😊 ◦ みんな Result を好きすぎて無限に Result の話をしている • でも Result<T, E> だけではツラいケースがある 😰 ◦ ローディング状態をどう表現する? ◦ エラーの中身をどうやって分ける? ◦ 使う側(コンポーネント)で細かい判定が必要で扱いにくい? ⇩ 判別可能なユニオン型を利用して、状態を正確にモデリングする方法を採用 (本質的には、外部への依存と内部実装とを切り分けている) 12
  5. パターン2. データ取得をカスタムフックに分離する • 👍 データ取得と表示の責務が分離された • 👍 コンポーネントのテストが楽になった ◦ 通信ではなくカスタムフックをモックすれば良い

    • 🔥 コンポーネントでの表示のために相変わらず色々と気にする必要がある ◦ 例:ローディングの判定のために特定のフラグを見る必要がある 14
  6. パターン3. 汎用 Result (neverthrow) を使ってみる • 👍 コンポーネントでの表示時に気にすることが減った ◦ 特定の値(フラグ)を元にした判定が不要

    • 🔥 ユニオン型と Result の使い分けが必要 ◦ 成功/失敗は Result だが、それ以外はユニオン型なので扱い方が異なる 15 ローディング中は undefined を 返すこととする
  7. ユニオン型を使う嬉しさ (1) • コンポーネントで switch 文によって状態ごとに容易に出し分けられる ◦ 複数の値を元にした状態の判別が不要 ◦ データの取得状態/結果とコンポーネントの表示を明確に関連付けられる

    • 網羅性チェックを行える ◦ パターン追加時の堅牢性が上がる 網羅性チェックを実施可能 switch 文により単純に出し分け可能 判別可能なユニオン 17
  8. ユニオン型を使う嬉しさ (4) • エラーの状態を細かく表現可能 その2 ◦ 例:HTTPステータスコードからコンポーネントを分離 ▪ エラーコードへ依存しないので、REST API

    から GraphQL へ切り替えるような 場合も、コンポーネントに手を入れる必要がない HTTPステータスコードを直接利用するのではなく、 エラー種別ごとに型を作って、ステータスコードに 依存しないようにする 20
  9. ユニオン型を使う嬉しさ (5) • エラーの状態を細かく表現可能 その3 ◦ 例:追加読み込みを行う API/コンポーネントにおいて、初回読込み時のエラーと追 加読込み時のエラーを区別する ▪

    状況によりエラーの表示方法を変えるのが容易(初回読込み時だとエラーペー ジ、追加読込み時だとスナックバー、など) ▪ 単純に useInfiniteQuery を使うより複雑になっているとも言えるが、何を 優先するかのトレードオフ次第 21
  10. ユニオン型を使う嬉しさ (6) • 実現方法の切替をコンポーネントと分離可能 ◦ 例:状態(履歴など)をサーバーに保存して全クライアントで同期したいが、ひと まず暫定的にローカルストレージへ保存して最低限改善する ▪ まずは暫定対応として、ローカルストレージへ保存するカスタムフックを実装 ▪

    バックエンドの準備が完了し次第、API 呼び出しによりサーバーへ保存するカ スタムフックを実装 ▪ この前後で、カスタムフックの型シグネチャ(特に成功/失敗を表す戻り値の ユニオン型)が変わらなければ、コンポーネントの実装ではこの切替を意識す る必要がない 22 Webフロントエンド バックエンド まずは暫定的に ローカル保存 バックエンドの準備完了後、 サーバーへ保存 コンポーネント 呼び出しの型シグネチャに変更がなければ 意識せずに切替可能
  11. まとめ • ZEN Study は10周年を迎えることができた 🎉 • 10年の積み重ねを踏まえつつ、次の10年に向けて改善を重ねている • TypeScriptの強力な機能である、判別可能なユニオン型を活用して、データ取

    得などの状態(外部への依存)を正確にモデリングすることにより、堅牢でメ ンテナンスしやすくできた • 他にも様々な工夫・改善を行なっています! 23 めでたし めでたし 🤟