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

フロントエンド開発に役立つクライアントプログラム共通のノウハウ / Universal ...

Avatar for nrs nrs
September 21, 2025

フロントエンド開発に役立つクライアントプログラム共通のノウハウ / Universal client-side programming best practices for frontend development

フロントエンドカンファレンス東京2025における発表資料です。
GUIクライアント開発のノウハウのうち、フロントエンド開発にも活かせるものについてお話しています

# URL
YouTube: https://www.youtube.com/c/narusemi
HomePage: https://nrslib.com
Twitter: https://twitter.com/nrslib
Instagram: https://www.instagram.com/nrslib/

Avatar for nrs

nrs

September 21, 2025
Tweet

More Decks by nrs

Other Decks in Programming

Transcript

  1. 51 • テストなんて書いても見た目が伴わなければ何の意味もないんだよ!(暴 論) ◦ 結果的にデバッガーという職種が発達してました ▪ デバッグ得意な人は開発者が嫌なところをよく知ってる人です • ちなみに私も得意でした

    ◦ 「なんかこのボタンぷるってすんなぁ!?」 ◦ 「どうせここを連打されるのが嫌なんだろ!?」 ◦ 「なんか1Fおかしいぞここ」 テスト? ねぇよそんなもん
  2. 52 • テストなんて書いても見た目が伴わなければ何の意味もないんだよ!(暴 論) ◦ 結果的にデバッガーという職種が発達してました ▪ デバッグ得意な人は開発者が嫌なところをよく知ってる人です • ちなみに私も得意でした

    ◦ 「なんかこのボタンぷるってすんなぁ!?」 ◦ 「どうせここを連打されるのが嫌なんだろ!?」 ◦ 「なんか1Fおかしいぞここ」 ◦ 最近は自動テストをやってるケースも増えてきたようだ テスト? ねぇよそんなもん
  3. 55 • データを操作するのは一箇所のみ • コンポーネントはめんどくさいと使われない • コンポーネントは気づかれないと使われない • StateMachine実装 •

    Mediatorパターン • イベントヘル • グローバルやめよ • : 今でも役立つゲーム開発で得たノウハウ フロントエンド開発でも 役立った!
  4. 59 • Single Source of Truth ◦ 信頼できる唯一の情報源 ◦ すべてのデータが一箇所のみで作成、あるいは編集できるようにする

    ◦ フロントエンドのための格言ではない ▪ が、意識するととってもいいぞ! SSOT
  5. 60 更新し忘れる Component A Component B Component C Component D

    Component F Name:nrs Name:nrs Name:nrs Name:nrs Name:nrs
  6. 61 更新し忘れる Component A Component B Component C Component D

    Component F Name:nrslib Name:nrs Name:nrs Name:nrs Name:nrs
  7. 62 更新し忘れる Component A Component B Component C Component D

    Component F Name:nrslib Name:nrslib Name:nrs Name:nrslib Name:nrslib
  8. 63 更新し忘れる Component A Component B Component C Component D

    Component F Name:nrslib Name:nrslib Name:nrs Name:nrslib Name:nrslib
  9. 64 実はもうちょっと深刻なんだ Component A Component B Component C Component D

    Component F Ptr: 1 Name:nrs Ptr: 1 Name:nrs Ptr: 2 Name:nrs Ptr: 1 Name:nrs Ptr: 1 Name:nrs
  10. 65 実はもうちょっと深刻なんだ Component A Component B Component C Component D

    Component F Ptr: 1 Name:nrslib Ptr: 1 Name:nrs Ptr: 2 Name:nrs Ptr: 3 Name:nrs Ptr: 1 Name:nrs
  11. 66 実はもうちょっと深刻なんだ Component A Component B Component C Component D

    Component F Ptr: 1 Name:nrslib Ptr: 1 Name:nrslib Ptr: 2 Name:nrs Ptr: 1 Name:nrslib Ptr: 3 Name:nrs
  12. 67 実はもうちょっと深刻なんだ Component A Component B Component C Component D

    Component F Ptr: 1 Name:nrslib Ptr: 1 Name:nrslib Ptr: 2 Name:nrs Ptr: 1 Name:nrslib Ptr: 3 Name:nrs
  13. 68 どうするかというと Component A Component B Component C Component D

    Component F Name:nrs Name:nrs Name:nrs Name:nrs Name:nrs
  14. 69 どうするかというと Component A Component B Component C Component D

    Component F Name:nrs Name:nrs Name:nrs Name:nrs Name:nrs 更新したい!
  15. 70 どうするかというと Component A Component B Component C Component D

    Component F Name:nrs Name:nrs Name:nrs Name:nrs Name:nrs 更新したい
  16. 71 どうするかというと Component A Component B Component C Component D

    Component F Name:nrslib Name:nrs Name:nrs Name:nrs Name:nrs
  17. 72 どうするかというと Component A Component B Component C Component D

    Component F Name:nrslib Name:nrs Name:nrs Name:nrs Name:nrs Name:nrslib Name:nrslib
  18. 73 どうするかというと Component A Component B Component C Component D

    Component F Name:nrslib Name:nrslib Name:nrslib Name:nrs Name:nrs Name:nrslib Name:nrslib
  19. 74 どうするかというと Component A Component B Component C Component D

    Component F Name:nrslib Name:nrslib Name:nrslib Name:nrs Name:nrs Name:nrslib Name:nrslib
  20. 75 どうするかというと Component A Component B Component C Component D

    Component F Name:nrslib Name:nrslib Name:nrslib Name:nrslib Name:nrslib
  21. type MyButtonProps = { text: string; primary: boolean; size: 'small'

    | 'medium' | 'large'; icon: string; iconPosition: 'left' | 'right'; borderRadius: string; padding: string; margin: string; fontFamily: string; fontSize: string; fontWeight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 'normal' | 'bold'; textTransform: 'none' | 'uppercase' | 'lowercase' | 'capitalize'; letterSpacing: string; lineHeight: string | number; shadow: boolean; shadowColor: string; shadowBlur: number; onClick: (event: React.MouseEvent<HTMLButtonElement>) => void; } これは極端な例だが……
  22. 91 • Progressive Disclosure ◦ ユーザーが必要な時に必要な情報や機能にアクセスできるようにする • 成功のポイント ◦ 80/20ルール

    ▪ 80%のケースで使われる機能を20%の労力で使えるよう ◦ 段階的な複雑性 ▪ 簡単なことは簡単に、複雑なことも可能に ◦ Sensible Defaults ▪ 殆どの場合でそのまま使える めんどくさいと使われない
  23. 98 <template> <normal-template> <template slot="main-content"> <h2 style="color: green">Play Ground</h2> <link-button

    :to="{name: 'module-playground-dropdown'}">Dropdown</link-button> <link-button :to="{name: 'module-playground-table'}">Table</link-button> <link-button :to="{name: 'module-playground-modal'}">Modal</link-button> <link-button :to="{name: 'module-playground-tab'}">Tab</link-button> <link-button :to="{name: 'modulePlaygroundPassword'}">Password</link-button> <link-button :to="{name: 'modulePlaygroundButton'}">Button</link-button> <link-button :to="{name: 'modulePlaygroundText'}">Text</link-button> <link-button :to="{name: 'modulePlaygroundFormElement'}">FormElement & Validator</link-button> <link-button :to="{name: 'modulePlaygroundDatepicker'}">datepicker</link-button> <link-button :to="{name: 'modulePlaygroundLoading'}">L@@ding</link-button> <link-button :to="{name: 'modulePlaygroundProgressTracker'}">ProgressTracker</link-button> <link-button :to="{name: 'modulePlayGroundProgress'}">Progress</link-button> <link-button :to="{name: 'modulePlayGroundCheckbox'}">Checkbox</link-button> <link-button :to="{name: 'modulePlayGroundPanel'}">Panel</link-button> <link-button :to="{name: 'modulePlaygroundVariableInput'}">VariableInput</link-button> <link-button :to="{name: 'modulePlaygroundRadioGroupButton'}">RadioGroup</link-button> <link-button :to="{name: 'modulePlayGroundClipboard'}">Clipboard</link-button> <link-button :to="{name: 'modulePlaygroundInnerWizard'}">InnerWizard</link-button> <link-button :to="{name: 'modulePlayGroundTooltip'}">Tooltip</link-button> <link-button :to="{name: 'modulePlayGroundConfigurationTipsPage'}">ConfigurationTips</link-button> <link-button :to="{name: 'modulePlayGroundPager'}">Pager</link-button> <link-button :to="{name: 'modulePlayGroundAnchor'}">Anchor</link-button> <link-button :to="{name: 'modulePlayGroundCapacityDisplay'}">Capacity</link-button> <link-button :to="{name: 'modulePlayGroundEmptyPanel'}">EmptyPanel</link-button> <link-button :to="{name: 'modulePlayGroundCarouselPage'}">Carousel</link-button> <link-button :to="{name: 'modulePlayGroundGraphPage'}">Graph</link-button> <router-view></router-view> </template> </normal-template> </template> <script lang="ts"> import { Component } from 'vue-property-decorator'; import { BasePage } from '@lib/vue/BasePage'; import NormalTemplate from 'front/atomic/Templates/Layer/NormalTemplate.vue'; import LinkButton from 'front/atomic/Atoms/Button/LinkButton.vue'; @Component({ components: { LinkButton, NormalTemplate }, }) export default class ModulePlayGroundRootPage extends Vue {} </script>
  24. 112 • オブジェクトごとに主行動はひとつである ◦ 毎フレーム更新される(俗に言うゲームループ) ◦ イメージ:フレームごとにオブジェクトは1処理を行う ▪ 走るボタンとガードボタンとポーズボタンが同時に押されたら? •

    優先度が高いものだけを実行する! ◦ 純粋なイベントドリブンにすると ▪ 優先順位を制御しづらい ▪ その行動が可能かの確認処理が散在する ゲームオブジェクト制御
  25. type GamePhase = "mainMenu" | "gamePlay" | "pause" | "gameOver"

    | "victory"; class GameCoordinator { private phase: GamePhase = "mainMenu"; constructor() { // 初期化処理 } start(): void { this.step("mainMenu"); } private step(phase: GamePhase): void { switch (phase) { case "mainMenu": this.stepMainMenu(); break; case "gamePlay": this.stepGamePlay(); break; case "pause": this.stepPause(); break; case "gameOver": this.stepGameOver(); break; case "victory": this.stepVictory(); break; } this._phase = phase; } private stepMainMenu(): void { // Main Menu を開始 } private handleStartButtonClick(): void { this.step("gamePlay"); }
  26. const TabButton = forwardRef<TabButtonHandle, { id: string; label: string; onRegister:

    (id: string, ref: React.RefObject<TabButtonHandle>) => void; }>(({ id, label, onRegister }, ref) => { const [isActive, setIsActive] = useState(false); const otherButtons = useRef<Map<string, React.RefObject<TabButtonHandle>>>(new Map()); const selfRef = useRef<TabButtonHandle>(null); ... const handleClick = () => { otherButtons.current.forEach(buttonRef => { buttonRef.current?.deactivate(); }); setIsActive(true); }; return (<button>...</button>); });
  27. const TabButton = forwardRef<TabButtonHandle, { id: string; label: string; onRegister:

    (id: string, ref: React.RefObject<TabButtonHandle>) => void; }>(({ id, label, onRegister }, ref) => { const [isActive, setIsActive] = useState(false); const otherButtons = useRef<Map<string, React.RefObject<TabButtonHandle>>>(new Map()); const selfRef = useRef<TabButtonHandle>(null); ... const handleClick = () => { otherButtons.current.forEach(buttonRef => { buttonRef.current?.deactivate(); }); setIsActive(true); }; return (<button>...</button>); });
  28. const TabButton: React.FC<{ id: string; label: string; isActive: boolean; onClick:

    (id: string) => void; }> = ({ id, label, isActive, onClick }) => { return ( <button onClick={() => onClick(id)}> {label} </button> ); };
  29. const TabButtonGroup: React.FC<{ tabs: Array<{ id: string; label: string }>;

    activeTab: string; onTabChange: (id: string) => void; }> = ({ tabs, activeTab, onTabChange }) => { return ( <div> {tabs.map(tab => ( <TabButton key={tab.id} id={tab.id} label={tab.label} isActive={activeTab === tab.id} onClick={onTabChange} /> ))} </div> ); };
  30. 129 • 相互参照 ◦ 5個のボタンなら 5*4 = 20 の参照 ◦

    O(n^2) の複雑度 • Mediator ◦ 5個のボタンなら 5 = 5 の参照 ◦ O(n) の複雑度 動作イメージ
  31. 130 • 相互参照 ◦ 5個のボタンなら 5*4 = 20 の参照 ◦

    O(n^2) の複雑度 • Mediator ◦ 5個のボタンなら 5 = 5 の参照 ◦ O(n) の複雑度 • どういう違い? ◦ どのボタンがどのボタンを変更したか追跡困難 動作イメージ
  32. 136 Event Driven • Event Dispatcher ◦ すごいぞ! ▪ dipatchEvent

    ▪ どのオブジェクトもイベント送出できるぞ!
  33. 137 Event Driven • Event Dispatcher ◦ すごいぞ! ▪ dipatchEvent

    ▪ どのオブジェクトもイベント送出できるぞ! ◦ すごいぞ!
  34. 138 Event Driven • Event Dispatcher ◦ すごいぞ! ▪ dipatchEvent

    ▪ どのオブジェクトもイベント送出できるぞ! ◦ すごいぞ! ▪ addEventListener
  35. 139 Event Driven • Event Dispatcher ◦ すごいぞ! ▪ dipatchEvent

    ▪ どのオブジェクトもイベント送出できるぞ! ◦ すごいぞ! ▪ addEventListener ▪ どのオブジェクトもイベント受け取れるぞ!
  36. 151 とっても自由です! • 限度があるやろ!!! ◦ 1F の間にイベントが飛び交う ◦ グローバルにイベントが送出されて ◦

    イベントを受け取ったオブジェクトがイベントを送出して ◦ 同時に別のオブジェクトからイベントが送出されて
  37. 152 とっても自由です! • 限度があるやろ!!! ◦ 1F の間にイベントが飛び交う ◦ グローバルにイベントが送出されて ◦

    イベントを受け取ったオブジェクトがイベントを送出して ◦ 同時に別のオブジェクトからイベントが送出されて ◦ 入り組みながらイベントが送出されつづけ
  38. 153 とっても自由です! • 限度があるやろ!!! ◦ 1F の間にイベントが飛び交う ◦ グローバルにイベントが送出されて ◦

    イベントを受け取ったオブジェクトがイベントを送出して ◦ 同時に別のオブジェクトからイベントが送出されて ◦ 入り組みながらイベントが送出されつづけ ◦ 「このイベントはどこからきてるねん!?」
  39. 169 それはとても緻密な • public static がふんだんに使われた奇跡のようなコード ◦ 「このコードを書いた人間は天才に違いない」 ▪ 緻密に編み込まれたジェンガ

    ◦ 脳内メモリの容量が違いすぎる ▪ 1F内にこの static なフィールドが何回書き換わってるんだ!? • データ更新したはずが書き換わってない!?
  40. 170 それはとても緻密な • public static がふんだんに使われた奇跡のようなコード ◦ 「このコードを書いた人間は天才に違いない」 ▪ 緻密に編み込まれたジェンガ

    ◦ 脳内メモリの容量が違いすぎる ▪ 1F内にこの static なフィールドが何回書き換わってるんだ!? • データ更新したはずが書き換わってない!? ◦ (入念な更新をされているパターン)
  41. export const OrderHistoryPage: React.FC = () => { const navigate

    = useNavigate(); const orders = [ { id: '1', productName: 'MacBook Pro', status: 'delivered' as const, total: 298000 }, { id: '2', productName: 'iPad Air', status: 'shipped' as const, total: 92800 } ]; const handleViewDetail = (orderId: string) => { navigate(`/orders/${orderId}`); }; const handleRequestInvoice = (orderId: string) => { navigate(`/orders/${orderId}/invoice`); }; return ( <div> <Header /> {/* 固定的な遷移は自己完結 */} <h1>注文履歴</h1> {orders.map(order => ( <OrderItem key={order.id} order={order} onViewDetail={() => handleViewDetail(order.id)} {/* 文脈的な遷移は制御 */} onRequestInvoice={() => handleRequestInvoice(order.id)} /> ))} </div> ); };
  42. 184 • 個人的なルール ◦ グローバルナビゲーション ▪ コンポーネントに埋め込む ◦ コンテキスト依存 ▪

    ページ相当のコンポーネントで制御する • 結果として ◦ ページ相当のコンポーネントを見れば、どう遷移するかわかる! 遷移の性質
  43. 186 遷移の情報はどこに置く? Page Component Component B Component C Component D

    Component F Transition Information Transition Information Transition Click!
  44. 198 よくある朝令暮改 Main Component A Component B Component C Component

    D Name:nrs Component E Name:nrs ここに表示 することになった Name:nrs ここに表示 することになった
  45. 202 • IObservavle<T>を保持 爆速目指そう Main Component A Component B Component

    C Component D Name:nrs Component E Component C Component E
  46. 203 • もし新たに必要になったら 爆速目指そう Main Component A Component B Component

    C Component D Name:nrs Component E Component C Component E Name:nrs ここに表示 することになった
  47. 204 爆速目指そう Main Component A Component B Component C Component

    D Name:nrs Component E Component C Component E IObservavle<T> を実装継承
  48. 213 まとめ • GUIのノウハウを活用すれば ◦ 実装に困った時に基本に立ち返られる ◦ ライブラリが滅んでも ▪ ARV

    がなくても秩序をもたらすことができる ▪ jQuery でも確実なコードを書ける ▪ JavaScriptだけしか使えない状況も乗り越えられるぞ
  49. 214 仲間を探しています 子どもを取り巻く環境をテクノロジーの力でよりよいものにしていく仲間を大募集! • 子育てしやすい環境です ◦ 子育てにドメイン知識がありすぎるメンバー ◦ フレックス/フルリモート可 •

    開発者体験が最高です ◦ アジャイル(XP) ◦ バーチャルオフィス(Gather) ◦ コンフォートゾーンを抜け出せるような環境 ◦ CTOが財布 #CTOは財布