- Published on
換過三個產業之後,我對「前端該學什麼」的答案變了三次
- Authors
-
-
- Name
- Alex Yu
-
前陣子在 Threads 看到一則貼文。發文的人做全端,公司沒有架構、沒有 code review,也沒有前輩可以問,最後一句是「自己再繼續看技術,也不知道看的方向對不對」。
底下有人回,說學了也沒用,現在隨便一個人叫 AI 寫一寫就好了。
我沒有回那則,因為想講的東西一則留言塞不下。我換過三個產業,每換一次,「前端該學什麼」的答案就被換掉一次。
而且每次被換掉的,都是我上一個階段最拿手的東西。
第一份工作:先把單向資料流走完一遍
2018 年的第一份前端工作,做的是面向一般大眾的行動 App。使用者量大,畫面互動多。
那幾年的順序有點反過來——先做 React Native 的跨平台 App,2021 年才回頭做 Vue 和 React 的網頁版。
歷史數據走勢圖
把一組按時間排列的數字畫成折線圖,畫面上可以切 30、50、100、200 筆。用 Canvas 畫。
格子大小固定,其中幾個點要套上一個紅圈標出來。最直覺的做法是拿格子中心當圓心,連線的端點也用同一個中心點。
畫出來就知道不對——線會直接穿過紅圈,看起來像把圓劃破了。
線的起點要落在圓周上,不是圓心。所以每一段連線都得先用 Math.atan2 算出兩個圓心的夾角,再用 cos/sin 乘上半徑,推出真正該落筆的座標。
那份專案的 code 我早就沒有了,只記得這個公式。
跨頁資料的雙索引
另一個功能是在單頁呈現一份很大的清單,右側做兩層索引:A–Z,加上中文的筆劃數(1 到 10 劃)。中文沒有字母順序,第二層只能拿筆劃來當索引。
資料是分頁載入的。但索引一點下去,使用者可能直接從第 2 頁跳到第 18 頁——逐頁補太慢,全部載進來又太重。
當時的解法是把「區間」這件事交給 API:
// 前端同時告訴 API「我現在在哪」「我要去哪」,以及使用者點的是哪一個索引
interface PageRangeRequest {
currentPage: number
targetPage: number
index: string // 'A'–'Z',或中文的筆劃數 '1'–'10'
}
// API 回傳整個區間,不是單頁
interface PageRangeResponse {
pages: Array<{ page: number; items: Item[] }>
}索引字元要跟著送,因為兩層索引指向的是不同的分頁切法——同一個 targetPage 在 A–Z 和筆劃底下不是同一批資料。
前端拿到之後自己把區間併進已有的資料。索引點到還沒有資料的位置時,才會再打一次。
代價是跳得越遠、一次拉的越多。當時說得過去的理由是時機——全部載進來是每個人一進頁面就付這個成本,區間載入只有真的去點索引的人才付。
這兩個功能真正給我的東西
它們長得完全不一樣,但卡住的地方是同一條路徑:
UI 觸發 action → 打 API → 拿回 data → 進 state → UI 監聽到改變 → re-render這條路徑有名字,叫單向資料流(unidirectional data flow),出自 Facebook 2014 年提出的 Flux。核心規則只有一條:資料只能往一個方向跑,畫面是 state 的函數,你不能反過來從畫面去改資料。
它要解決的是前一代的 MVVM 雙向綁定——畫面可以改資料、資料也可以改畫面,出事的時候查不出是誰改的。Flux 把方向鎖死,代價是要多寫一些樣板,換來的是每次資料變動都有跡可循。
當時我不知道它有名字,只知道每次卡住都卡在同一個地方。走勢圖畫不出來的時候,我得先判斷是資料還沒進 state,還是進了但沒觸發重繪。雙索引也一樣,跨頁併完之後畫面沒更新,問題永遠落在這條線的某一段。
後來換 React Native、自學 Angular、現在寫 Next.js,這條路徑一次都沒變過。變的只有語法。
語法各自不同,規則是同一條
先看最常見的一種:一份清單,要依關鍵字篩選後顯示。這個結果不用另外存,從原本的清單算出來就好:
// Vue 3:computed
const filtered = computed(() => list.value.filter((i) => i.name.includes(kw.value)))
// React:useMemo
const filtered = useMemo(() => list.filter((i) => i.name.includes(kw)), [list, kw])
// Angular 16+:computed signal
filtered = computed(() => this.list().filter((i) => i.name.includes(this.kw())))副作用也是。資料變了要去打 API:
// Vue 3:watch
watch(kw, (v) => fetchSuggestions(v))
// React:useEffect
useEffect(() => { fetchSuggestions(kw) }, [kw])
// Angular 16+:effect
effect(() => fetchSuggestions(this.kw()))三段 code 沒有一行長得像,但底下是同一條規則:能算出來的值就用算的,不要再存一份 state;副作用不要塞進 render;很吃效能的計算要把 dependency 列清楚,讓框架決定什麼時候該重算。
同一件事在單一框架的版本之間也成立。我 2021 年寫的 Vue 2 是把它們放在 computed: {} 和 watch: {} 兩個區塊裡,Vue 3 改成 computed() 和 watch() 兩個函式——名字沒變,只是位置從物件的欄位換成了呼叫的函式。
寫壞的後果也一樣。多存一份 state 就會有兩份資料對不起來,dependency 沒列好就會每次 render 都重跑一次——這在三個框架裡都是同一種 bug,只是報錯的樣子不同。
所以「這個框架我沒用過」在面試時是問題,實際做起來通常不是。要花時間的是搞清楚它的語法對應到哪一條規則,而那張對應表比語法本身短得多。
物流 B2B:一個人寫得對,不夠
2023 年進 Morrison Express 做 B2B 的物流系統,帶五個前端。
使用者換了。不再是匿名的大眾,是內部固定的那批人,他們會被教育怎麼操作。但業務邏輯錯一個欄位,下游整條流程都會歪。
「對」的定義跟著換。單一畫面對不對已經不是重點,重點是業務邏輯對不對、五個人能不能一起改同一份 code,以及十幾個畫面能不能長得像同一套系統。
把需求切成別人能接手的大小
帶五個人之後,第一件要學的事是拆任務。
需求進來的時候是一整塊——「訂單管理要能查、能改、能匯出」。這種東西沒辦法直接丟給人,尤其是剛進來的。
拆得太粗,對方卡住了也講不出自己卡在哪,只能等我有空;拆得太細,每個人手上都只剩一小塊,拼起來才發現介面對不上。
後來抓到的判準是:每一塊都要能自己驗收。做完之後那個人自己就知道對不對,不用等我看過才敢往下走。
還有一件事是拆的時候順便決定了對方學到什麼。把最麻煩的部分都留給自己,進度確實比較快,但一年後團隊裡還是只有我會那一塊。
畫面說穿了都是 CRUD,難的是讓它們長得一樣
B2B 系統的畫面大多是 CRUD:一張列表、一組篩選、一個新增/編輯的表單。訂單、報價、客戶資料——換個名詞就是同一套流程。
正因為長得像,各寫各的代價才特別高。同一個使用者換一個頁面,篩選器換了位置、儲存按鈕從右上跑到底部,他就得重新學一次。B2B 的使用者天天在用同一套系統,這種不一致每天扣一點。
但也不能全部統一,因為業務邏輯沒那麼聽話。
單據有好幾種、各自跑不同流程;同一種單據換一個大客戶,又有自己的規則要吃。再加上「一個大單號底下掛好幾個小單號」這種階層,同一批貨最後可能分送到不同地方。
這些塞不進同一張通用表單。
所以要拿捏的是同一件事的兩端——骨架統一到什麼程度,例外開放到什麼程度。開太少,有人受不了就複製一份骨架自己改,之後骨架修的 bug 不會跟著修;開太多就等於沒統一,每頁還是各長各的。
這個問題在上一個階段完全不存在。一個人寫一個功能的時候,「一致」是自己記得住的事。
開始會問「這個需求合理嗎」
在那之前,需求進來我就做。規格寫什麼就實作什麼,做完算交差。
到這個階段不夠用了。使用者是內部固定的那批人,他們每天怎麼操作是看得到、也問得到的;而規格是從業務目標推出來的,不一定等於他們手上真正的動線。
常見的落差像是欄位順序跟他們填單的順序相反,或者一個流程被切成三頁,但他們實際上是一次做完。這些在規格上看不出來,要去看他們操作才會發現。
所以得先看懂業務流程,才有辦法判斷一個需求該不該長這樣。工程師的角色在這裡多了一層——交付之前,要先確認這個設計對使用者是不是真的好用。
這件事後來我才知道有名字。設計思考裡的「雙鑽石模型」把產品開發切成四段:
圖:雙鑽石模型出自英國 Design Council;中文標註與 Senior/Junior 的分界依莫力全 Kyle Mo 的職涯反思一文重繪。
鑽石的形狀就是發散再收斂:先把可能性打開,再收成一個決定。工程師的預設位置在第二顆——需求已經定案,我負責想技術方案、再把它做出來。
那篇文章裡有一句話我看了很有感:「如果你的工作中只聚焦在這塊鑽石上,那你的能力很可能就停留在 Junior Level 而已。」
我在物流那幾年的變化,說穿了就是從只待在第二顆,變成偶爾能參與第一顆。
沒有人開需求單的那一塊
第三件事是專案本身。
需求做完了、也確認過合理了,還有一層沒人管:build 一次要多久、CI 跑一輪要多久、使用者打開頁面要等多久。這些不會有人開單給你,但它們每天都在扣掉所有人的時間——扣的是團隊的,也是使用者的。
我那幾年卡住的地方就在這裡:循環相依、build 時間、每個 repo 各跑各的 CI,還有前端本身的效能。這段寫成了一個十篇的系列,這裡不重講。
媒體:沒有人會告訴你壞了
現在在 TVBS 做健康2.0,一整條產品線只有我一個前端。
使用者又變回匿名大眾,但量級不同。接手時日均 pageview 大約 40 萬,現在 60 萬以上。
這裡多了兩件以前不用管的事:搜尋引擎找不找得到我的頁面,還有東西壞掉的時候有沒有人會發現。
Morrison 以前我做的東西都不需要被搜尋到——第一份工作是 App,物流是內部系統。所以 SSR 一直不在我的工具箱裡。Next.js 2016 年就有 SSR,跟它紅不紅沒關係,是我用不到。
到了媒體業,SEO 直接變成第一順位,SSR 才第一次跟我有關。
後面那件更麻煩。沒有人 review 我的 code,線上壞掉的時候也不會有人先告訴我。爬蟲三分鐘打出七千個錯誤、K8s 的時區讓日期判斷晚了八小時——這些都是自己撞出來的。
換的不只是我,框架也在換
答案會變,不完全是因為我換了產業。同一段時間裡,前端要解決的問題本身也在換。
2018 年前後是 SPA 的高峰。前後端分離、後端只出 API、前端接管路由和渲染——那幾年「會 SPA」幾乎等於「會前端」。
SPA 把互動體驗做起來了,但留下一筆帳:首屏要等 JS 下載執行完才有東西,而爬蟲不一定等你。Next.js 和 Nuxt 2016 年就補上 SSR 和 SSG 把渲染時機往前挪,但要等到 SEO 變成很多產品的生存問題,它們才真的紅起來。
再後來是 Astro 和 Svelte 這一輪,針對的是「內容型網站根本不需要送那麼多 JS」。Astro 預設一行 JS 都不送,只在需要互動的地方開島;Svelte 在編譯期就把元件轉成直接操作 DOM 的 code,瀏覽器裡不用再跑一套 virtual DOM。
每一代框架都在還上一代留下的帳。所以「該用哪個框架」沒有標準答案,要看需求落在哪一段:要不要被搜尋到、首屏能等多久、互動有多重。
這個部落格今年從 Next.js 搬到 Astro,理由就是這個——它是內容站,一篇文章需要的 JS 遠比一個 App 少。同樣的判斷拿去看一個天天在用的後台系統,答案會完全相反。
上一份工作的能力,是下一份工作的門檻
每換一次,我最拿手的那一套就降一級——不是變成沒用,是變成「本來就該會」。
單向資料流在第一份工作是我的全部,到第二份只是入場條件,沒有人會因為你會這個而覺得你厲害。帶人、拆任務、code review 在第二份是核心,到第三份剩一半用得上,因為沒有人可以帶。
但降級不等於作廢。單向資料流我到現在每天還在用——四個框架、三個產業,同一套模型。它只是不再讓我顯得厲害而已。
回到那則貼文。「不知道看的方向對不對」,我的答案是方向本來就會變。現在覺得學的東西沒用,很多時候只是所在的環境還用不到它。等環境換了,它會自己回來。
還有留言區那句「叫 AI 寫一寫就好了」。
會前端當然還是重要的。但這三段路走下來,每一次真正卡住我的地方都不在前端裡面——第一段是資料怎麼流,第二段是五個人怎麼共用一份 code,第三段是搜尋引擎怎麼看我的頁面、容器給我的時區是哪一個。
前端能力是入場券。卡住的地方在旁邊那一圈。
所以在 AI 什麼都能寫一點的現在,我會把力氣放在把那一圈拉寬。具體是這些:
- 量得出來的效能——LCP、INP、CLS 怎麼從紅色拉到綠色,Lighthouse 報告裡哪幾條值得修、哪幾條可以無視
- 圖片——格式、尺寸、要不要 lazy load、佔位符怎麼給。內容站的 LCP 兇手八成在這裡
- 搜尋引擎怎麼看我的頁面——SSR 跟 CSR 對它的差別、爬蟲吃不吃得到我渲染出來的東西
- 依賴的版本——升一版會不會壞、不升會不會有漏洞,哪個警告值得停下來處理
- 服務實際跑在哪裡——用了 SSR 就得有一台一直跑著的機器。於是變成 pod 的資源怎麼配、ALB 回 5xx 的時候 log 去哪裡撈、Grafana 上先看哪一條線
而這五項只是我剛好碰到的。無障礙要做到什麼程度才算及格、多語系除了換字串還要處理什麼、即時協作的畫面怎麼同步、WebAssembly 什麼時候值得用——這些我都還沒真的做過,但它們一樣在這一圈裡面。
這些 AI 都能給你一段 code。但它不知道你的頁面現在 LCP 是幾秒、哪張圖是兇手、哪個漏洞警告對你的專案真的有風險。它會寫,但它量不到你的系統。
最極端的版本是被打的時候。爬蟲三分鐘噴出七千個錯誤那次,能做的事情全部落在這一圈裡:先判斷這是攻擊還是自己壞掉、rate limit 該掛在 IP 還是別的東西上、哪幾條路徑要先擋。那段時間 AI 幫得上的忙很有限,因為它不知道我的流量平常長什麼樣——而那正是唯一能拿來比對的基準。
這跟轉職沒關係。那一圈本來就是前端的一部分,只是沒有人會在面試的時候問。
延伸閱讀
- 一個人扛一整個網站時,我學會放掉什麼——現在這個階段的日常長什麼樣
- 同樣用 AI 寫 code,差距為什麼越拉越開?——那串留言區在吵的另一半
- 前端廣告系統設計:使用 Event-Driven Architecture 實現廣告位互斥管理——媒體站那層「對」的具體案例
- 從 Dependabot 換到 Renovate:為什麼我不把漏洞修完——上面「哪個警告值得停下來處理」的實際判斷