Slide 1

Slide 1 text

10周年を迎えた ZEN Study Webフロントエンドの 次の10年へ向けた工夫 株式会社ドワンゴ 教育事業 (小松) 2026/05/23 TSKaigi 2026 スポンサーセッション

Slide 2

Slide 2 text

自己紹介 ● ドワンゴ 教育事業本部 e-learningプロダクト開発部   ZEN Study Webフロントエンドセクション 小松(こまつ) ● ここ2年ほど ZEN Study の開発に従事 ● 趣味の1つ:  ドライブ【距離ガバ】  日本本土の海沿いを走破+α 2 ニコニコ超会議の 配信に映り込む 不審なスタッフ

Slide 3

Slide 3 text

本セッションのサマリー ● ドワンゴ教育事業とZEN Studyの概要 ○ 事業・サービス ○ これまでの10年のごく簡単な振り返り ● 次の10年に向けてメンテナンス性を向上するために行なっている取り組みの一 部を、主にTypeScriptの観点から紹介 ○ 外部とのやり取りの結果として、判別可能なユニオン型を活用し、状態を詳細にモ デリング (お昼休憩中なので、頭を使う難しい話ではなく、基本的な話をします) 3

Slide 4

Slide 4 text

ドワンゴの教育事業とは (1) ● 主にN高グループとZEN大学に対して、教育に関するシステムを提供 N高グループ(N・S・R高) KADOKAWAとドワンゴが創るネットの高校 2016年4月開校(N高) 生徒数 3万人規模 ZEN大学 日本財団とドワンゴが創るオンライン大学 2025年4月開学 学生数 7千人規模 4

Slide 5

Slide 5 text

ドワンゴの教育事業とは (2) ● 生徒・学生が直接使用するアプリや、学校の運営に関するシステムを提供 ○ 学習アプリ ZEN Study ■ 各学校向けだけではなく、一般向けにも月額課金で提供 ○ 教材作成・学校運営に関連するシステム 5

Slide 6

Slide 6 text

ZEN Study 10周年 🎉 ● 2016年4月に「N予備校」としてサービス開始 10周年記念サイト https://www.nnn.ed.nico/pages/10th_anniversary サービスの歴史 ドワンゴ教育サービス開発者ブログ https://blog.nnn.dev/ 10年を技術面から振り返る記事 6

Slide 7

Slide 7 text

10年の歴史がある = 10年の積み重ねがある ● 最初の構築が急ピッチで行なわれた ○ N高の開校と同時にリリースする必要があった ■ 期限が決まっている中で、完全新規プロダクトの構築が行なわれた ○ 開発スピードを優先した選択が行なわれた ■ メンテナンス性の優先度は低かった ● 複雑性が積み重なっている ○ 新機能の追加 ○ 対象となる学校の増加 ■ S高・R高の開校、大学の開学 ○ リファクタリングが追い付いていないコード 7

Slide 8

Slide 8 text

ZEN Study 関連の開発体制 ● フロントエンドはプラットフォームごとに分かれている ● バックエンドは別チーム 8

Slide 9

Slide 9 text

ZEN Study Webフロントエンドの主な技術スタック ● TypeScript + React SPA ○ (Reactを10年使っているサービスは古参と言えそう) ● TanStack Query ● React Router ● React Redux 9

Slide 10

Slide 10 text

ZEN Study Webフロントエンド 10年間の技術変遷 次の10年を見据えて、コードベースの改善を継続して行なっている 10

Slide 11

Slide 11 text

次の10年に向けて メンテナンス性を向上するための 取り組み(の一部) 11 判別可能なユニオン型を利用して、状態を正確にモデリングする

Slide 12

Slide 12 text

判別可能なユニオン型を利用する方法の概要 ● 外部 API 呼び出しの結果は、成功/失敗のケースがある ● それなら汎用的な Result を使うのが良さそう 😊 ○ みんな Result を好きすぎて無限に Result の話をしている ● でも Result だけではツラいケースがある 😰 ○ ローディング状態をどう表現する? ○ エラーの中身をどうやって分ける? ○ 使う側(コンポーネント)で細かい判定が必要で扱いにくい? ⇩ 判別可能なユニオン型を利用して、状態を正確にモデリングする方法を採用 (本質的には、外部への依存と内部実装とを切り分けている) 12

Slide 13

Slide 13 text

パターン1. コンポーネントで直接データを取得する ● 🔥 データ取得と表示の責務が分離されていない ● 🔥 コンポーネントのテストが大変 ○ 通信をモックする必要がある 13

Slide 14

Slide 14 text

パターン2. データ取得をカスタムフックに分離する ● 👍 データ取得と表示の責務が分離された ● 👍 コンポーネントのテストが楽になった ○ 通信ではなくカスタムフックをモックすれば良い ● 🔥 コンポーネントでの表示のために相変わらず色々と気にする必要がある ○ 例:ローディングの判定のために特定のフラグを見る必要がある 14

Slide 15

Slide 15 text

パターン3. 汎用 Result (neverthrow) を使ってみる ● 👍 コンポーネントでの表示時に気にすることが減った ○ 特定の値(フラグ)を元にした判定が不要 ● 🔥 ユニオン型と Result の使い分けが必要 ○ 成功/失敗は Result だが、それ以外はユニオン型なので扱い方が異なる 15 ローディング中は undefined を 返すこととする

Slide 16

Slide 16 text

パターン4. カスタムフックが返す型としてユニオン型を利用 ● この方式を我々は採用 ● 👍 データ取得などの外部とのやり取りと表示の責務がより分離された ● 👍 数々の嬉しさがある(後述) 判別可能なユニオン 16

Slide 17

Slide 17 text

ユニオン型を使う嬉しさ (1) ● コンポーネントで switch 文によって状態ごとに容易に出し分けられる ○ 複数の値を元にした状態の判別が不要 ○ データの取得状態/結果とコンポーネントの表示を明確に関連付けられる ● 網羅性チェックを行える ○ パターン追加時の堅牢性が上がる 網羅性チェックを実施可能 switch 文により単純に出し分け可能 判別可能なユニオン 17

Slide 18

Slide 18 text

ユニオン型を使う嬉しさ (2) ● データ取得成功時のみ値があることが型で保証される ○ 値を取得できていない状態のときは、値にアクセスできない ● SUCCESS のときのみ値がある ● LOADING や ERROR のときには値がない ERROR などの状態で 値にアクセスを試みると 型エラーになる 18

Slide 19

Slide 19 text

ユニオン型を使う嬉しさ (3) ● エラーの状態を細かく表現可能 その1 ○ 例:リロードによる復旧可否を分ける リロードしても意味がないエラーと リロードにより復旧できるエラーを 別の型にする リロードボタンの表示を容易に出し分けられる 19

Slide 20

Slide 20 text

ユニオン型を使う嬉しさ (4) ● エラーの状態を細かく表現可能 その2 ○ 例:HTTPステータスコードからコンポーネントを分離 ■ エラーコードへ依存しないので、REST API から GraphQL へ切り替えるような 場合も、コンポーネントに手を入れる必要がない HTTPステータスコードを直接利用するのではなく、 エラー種別ごとに型を作って、ステータスコードに 依存しないようにする 20

Slide 21

Slide 21 text

ユニオン型を使う嬉しさ (5) ● エラーの状態を細かく表現可能 その3 ○ 例:追加読み込みを行う API/コンポーネントにおいて、初回読込み時のエラーと追 加読込み時のエラーを区別する ■ 状況によりエラーの表示方法を変えるのが容易(初回読込み時だとエラーペー ジ、追加読込み時だとスナックバー、など) ■ 単純に useInfiniteQuery を使うより複雑になっているとも言えるが、何を 優先するかのトレードオフ次第 21

Slide 22

Slide 22 text

ユニオン型を使う嬉しさ (6) ● 実現方法の切替をコンポーネントと分離可能 ○ 例:状態(履歴など)をサーバーに保存して全クライアントで同期したいが、ひと まず暫定的にローカルストレージへ保存して最低限改善する ■ まずは暫定対応として、ローカルストレージへ保存するカスタムフックを実装 ■ バックエンドの準備が完了し次第、API 呼び出しによりサーバーへ保存するカ スタムフックを実装 ■ この前後で、カスタムフックの型シグネチャ(特に成功/失敗を表す戻り値の ユニオン型)が変わらなければ、コンポーネントの実装ではこの切替を意識す る必要がない 22 Webフロントエンド バックエンド まずは暫定的に ローカル保存 バックエンドの準備完了後、 サーバーへ保存 コンポーネント 呼び出しの型シグネチャに変更がなければ 意識せずに切替可能

Slide 23

Slide 23 text

まとめ ● ZEN Study は10周年を迎えることができた 🎉 ● 10年の積み重ねを踏まえつつ、次の10年に向けて改善を重ねている ● TypeScriptの強力な機能である、判別可能なユニオン型を活用して、データ取 得などの状態(外部への依存)を正確にモデリングすることにより、堅牢でメ ンテナンスしやすくできた ● 他にも様々な工夫・改善を行なっています! 23 めでたし めでたし 🤟