Michael Lo

Command Palette

Search for a command to run...

Blog
PreviousNext

RADIO Pattern

--

前端系統設計的結構化思考框架,從需求分析到優化的完整方法論

前端系統設計有沒有一套方法論可以依循?

什麼是 RADIO?

RADIO 是 Yangshun TayGreatFrontEnd 上發表的前端系統設計框架。

原本是針對系統設計面試設計的,但這套思考流程在日常開發也很實用——不管是規劃新功能、寫技術文件、還是跟同事討論架構都適用。

階段目標重點問題
Requirements釐清需求與範圍我們到底要做什麼?
Architecture識別關鍵元件及其關係系統長什麼樣子?
Data Model描述資料實體與欄位資料從哪來、存在哪?
Interface定義元件間的 API元件之間怎麼溝通?
Optimizations討論優化與深入探討怎麼做得更好?

R - Requirements(需求探索)

很多人拿到需求就開始畫架構圖,但需求本身往往是模糊的,「做一個 News Feed」這句話裡面有太多東西沒說清楚:

  • 要支援哪些功能?發文、按讚、留言、分享?
  • 要支援哪些裝置?桌面、手機、平板?
  • 效能要求是什麼?首屏載入要多快?
  • 有離線需求嗎?

Functional vs Non-functional

類型說明範例
Functional沒有就不能用使用者可以發文、可以按讚
Non-functional沒有也能用,但體驗差首屏 < 3 秒、滾動 60fps

先把 functional requirements 搞清楚,再來考慮 non-functional,不然會發現自己在優化一個根本不需要的功能。

核心功能 vs 加分項

實際開發中時間永遠不夠,把功能分成 must-have 跟 nice-to-have:

優先級功能
Must Have瀏覽貼文列表、發布文字貼文、按讚互動
Nice to Have圖片/影片上傳、即時通知、貼文編輯/刪除

A - Architecture(架構設計)

確認需求後開始畫架構圖,不需要很精美,重點是釐清元件之間的關係跟資料流向

常見的前端元件

元件職責類比
Server提供 API,處理資料持久化當成黑盒子就好
Controller發請求、轉換資料、處理錯誤交通指揮
Store管理應用程式狀態資料倉庫
View渲染 UI、處理使用者互動使用者看到的東西

範例:News Feed 架構

Frontend

React / Next.js
Feed UI貼文列表
Post Item單篇貼文
Composer發文表單

Client Store

Zustand / Redux
posts貼文資料
user使用者狀態
pagination分頁資訊

Server

REST API / GraphQL
GET /feed取得貼文
POST /posts發布貼文

每個元件都要能說清楚它的職責,如果說不清楚,可能是切得不對。


D - Data Model(資料模型)

資料分兩種:

類型來源特性範例
Server-originated後端 API需要持久化、多人共享使用者資料、貼文內容
Client-only前端產生不需要存到後端表單輸入、UI 狀態

Server Data 範例

typescript
interface Post {
  id: string;
  content: string;
  author: User;
  createdAt: number;
  reactions: {
    like: number;
    love: number;
  };
}
 
interface User {
  id: string;
  name: string;
  avatarUrl: string;
}

Client Data 範例

typescript
// 要送出去的資料(但還沒送)
interface DraftPost {
  content: string;
  image?: File;
}
 
// 純 UI 狀態(關掉頁面就沒了)
interface UIState {
  isComposerOpen: boolean;
  isLoading: boolean;
  error: string | null;
}

為什麼要分清楚?因為生命週期不同:Server data 要考慮 cache、stale、invalidation,Client data 通常 component unmount 就沒了。


I - Interface(API 定義)

這裡的 API 不只是 HTTP API,也包括 component props、store actions。

Server-Client API

typescript
// GET /api/feed?cursor={cursor}&limit={limit}
interface GetFeedResponse {
  posts: Post[];
  pagination: {
    nextCursor: string | null;
    hasMore: boolean;
  };
}
 
// POST /api/posts
interface CreatePostRequest {
  content: string;
  image?: string;
}

Client-Client API

typescript
// Store 提供的 interface
interface FeedStore {
  posts: Post[];
  isLoading: boolean;
 
  fetchFeed: (cursor?: string) => Promise<void>;
  createPost: (draft: DraftPost) => Promise<Post>;
}
 
// Component props
interface PostItemProps {
  post: Post;
  onReact: (type: ReactionType) => void;
  onComment: () => void;
}

定義清楚 interface 的好處是可以平行開發,前端可以先 mock 資料,不用等後端 API 好。


O - Optimizations(優化策略)

前面四個步驟是「讓它 work」,這步是「讓它 work well」,根據產品特性選擇要深入的領域:

產品類型優化重點
電商網站效能(首屏、LCP)、SEO
社群媒體無限滾動、即時更新
編輯器併發處理、undo/redo
Dashboard大量資料渲染、圖表效能

效能優化範例

虛擬列表:只渲染可見區域,處理長列表

typescript
import { useVirtualizer } from '@tanstack/react-virtual';
 
function FeedList({ posts }: { posts: Post[] }) {
  const virtualizer = useVirtualizer({
    count: posts.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 300,
    overscan: 5,
  });
 
  return (
    <div ref={parentRef}>
      {virtualizer.getVirtualItems().map((item) => (
        <PostItem key={posts[item.index].id} post={posts[item.index]} />
      ))}
    </div>
  );
}

Optimistic Update:先改 UI,再發請求

typescript
async function handleLike(postId: string) {
  // 1. 先更新 UI(使用者立刻看到效果)
  store.incrementLike(postId);
 
  try {
    // 2. 發請求
    await api.likePost(postId);
  } catch {
    // 3. 失敗就 rollback
    store.decrementLike(postId);
  }
}

這招用在按讚、收藏這種操作特別有效,使用者不用等 loading。

其他優化方向

領域常見做法
網路Request deduplication、SWR、Prefetch
渲染Lazy loading、Code splitting、Skeleton
無障礙語意化 HTML、鍵盤導航、ARIA labels
離線Service Worker、IndexedDB cache

小結

  • Requirements 花的時間永遠值得:問清楚再動手,比做完再改省時間
  • 畫圖比講話有用:架構圖不用漂亮,但要畫,很多時候畫出來才發現自己沒想清楚
  • 先 work 再 work well:不要一開始就想優化,先讓功能跑起來,再根據實際瓶頸優化
  • 深度優於廣度:每個領域都講一點,不如挑一個講透

延伸閱讀