# 前言 - 前端技術變化快速,每三個月就一個新週期,因此隨時會有更好的新東西出現是常態 - 並且原先認為優秀的技術隨著採用人數增加與時間拉長而被發現許多缺點最終被淘汰也是常見的事(例如 redux) - 這是為何前端開發似乎總在挑選新技術或在不同技術間切換的原因 - 好消息是累積十多年前端開發經驗後學了很多教訓,因此現在知道真正 work 的技術是哪些與正確的開發工法該如何安排 - 本課程的目地就是完整傳授此技巧 # 基礎開發心法 - 以下是從我個人工作經驗中總結的成功心法,經過多年第一線實戰確定安全可行 - 首先在`能達成目地的前提下`優先採用最`陽春、單純`的技術,絕不盲目追尋最新炫砲玩具 - 開發手法越`制式化`越 `boring` 越好,也就是所有事情都只有一種做法,而且是最不花俏的那種 - 最終目地是透過統一的開發工法讓每個人寫出來的東西都一樣 - 因此 code review 時能輕鬆讀懂對方的程式 - 也容易一眼抓出不對勁之處 - 總的來說`單純`+`boring` 帶來的好處是 - 實現最高境界讓巨大專案程式碼複雜度不隨時間等比級數增長而失控,反而能永遠維持在 O(1) 的複雜度 - 嚴格要求完整 test coverage 以確保產品穩定、安全、可靠 - 過去數年個人由此心法受益良多也衷心希望能籍由這次課程完整的傳授給各位 - 附帶一提我也是基於上開兩大原則來挑選這次要使用的技術,下面將詳述背後的理由 # 高層次概念 ![](https://i.imgur.com/LF7kmqu.png) - 前端開發核心架構(如上圖),本份文件將說明每個單項架構採用的技術與背後理由 - 狀態管理 - 資料獲取 - 路由器 - 元件應用 - 樣式 - 元件管理 - 設計系統 - 首先要理解 react 本身是很單純的東西 - 它之所以自稱為 `UI library` 而非 `framework` 意思就是它只負責繪出介面,其它都不管 - 更簡單講它就是一個 fn,餵入 data 後會吐出 html 元素 - fn(data) -> UI - 也就是一個會產生介面的函式 - 此處餵入的 data 一般稱為 `state` - `state` 的來源有兩種 - 本地產生:例如常見的 counter 範例,計數器數字是本地產生也只在本地更新 - 遠端產生:例如常見的 Todo 範例,需從外部 api 獲取 `todos[]`資料供操作 - 依上開思路可清楚看出除了`繪出介面`之外的事都應該乾淨切分於外部操作,例如 - 狀態管理 - 資料獲取 - 成功的前端開發基本心法最基本就是遵循`乾淨切分`與`單一原則` - 乾淨切分:每個核心架構各司其職,彼此互不干涉牽扯 - 單一原則:每個部件只做一件事並做到最好 - 本次課程也是依此原則設計,也是由此心法出發挑選 2022 年目前最好的選項 - 下面開始依序說明每個架構的功能與選取技術的理由 # 狀態管理(state) ## 角色 - 2022 年之後業界對於狀態管理的共識是走向`狀態`與`view`兩者應盡量切分乾淨 - `view` 是指 react component - 其中最有名例子就是 `redux` 它是一個外部狀態儲存器,然後透過 hooks 掛入 `view` 層 - `zustand` 與 `mobx` 則是另外兩個頗受歡迎的例子 - 它們的特色都是`狀態`完全獨立於 react view 之外管理,然後透過類似 `dispatch` 的指令觸發改變 ## 選項 ### redux - [文件](https://redux.js.org/) - 手法繁複要寫太多 boilerplate code 很煩又慢也難 debug ### react-context-selector - [文件](https://github.com/dai-shi/use-context-selector) - 當初選擇它是因為 React `v18` 將來會提供官方版的 `useContextSelector` - 但目前官方關於此功能的 proposal 仍然不斷變化,真正實作與推出日期遙遙無期 - 因此有用戶搶先推出自行開發的民間版,將來可無縫接軌換回官方版 - 原先人們對此民間版寄與厚望,但後來發現官方 prosal 的規格變化快速將來實際用法目前也不明瞭 - 因此投資時間學民間版風險就增加,畢竟將來官方版的設計可能完全不同 - 另方面則是大家很快發現它是綁死於 react context 也就是違返了上開`狀態與 ui 分離`原則,這是一個本質上的 flaw 完全無藥可救 - 這導致使用上要非常小心否則很容易觸發不必要的重繪並大量增加除錯時間 - 也因為易出錯這特性導致使用手法上有諸多限制,也不容易有彈性應用空間 - 簡單結論就是民間版實作的 api 未來變化性還太大,並且也較複雜難學 - 缺點 - 所有狀態還是全塞在 useState({...}) 內一大包,這是典型的 anti-pattern - setState() 可任意修改別人的屬性而非僅限自己這個屬性(例如誤改到),這會造成其它畫面產生不必要的重繪 - 總的來說是整體設計 `too flaw and too brittle` ### recoil - [文件](https://recoiljs.org/) - 臉書最新推出的狀態管理器,大原則是將狀態碎片化切成最小單位來個別儲存與應用 - 然後再視需求將多個小狀態組合(compose)成一包大狀態來應用,這個理念從本質上做對許多事 - 唯一可惜的是它為了滿足臉書內部眾多需求因此 api 設計的頗為繁雜而不易學 - 因此後來有人依它的理念推出簡化版的工具也就是 jotai ### jotai - [文件](https://jotai.org/) - 承襲 recoil 的精神與所有優點,但大幅簡化 api 並提供更高的擴充與組合彈性 - 最大高點是基礎概念極為簡單易學,任何人只要學會四個語法就能處理日常大部份需求 - 從架構上它最大優點是完全與 react context 切分,符合`狀態與 ui 分離`大趨勢 - 並且由於作者本身熟悉 FP 理念因此 api 設計上大量採用 FP 常見的 compose, optics 等手法 - 這些手法與設計讓 jotai 成為一個充滿彈性應用可能且優雅的狀態管理方案 - 簡單講它幾乎是 react 問市十年來最優秀的狀態管理方案,因為它幾乎解決了過往所有的問題並從根本上做對了一切細節! ## 結論 - 經與多名現任職於臉書的朋友討論,他們表示臉書內部也明白 react 18 建構中的 `context` api 有其侷限性,就算將來真完成了也無法完全解決 `re-render` 問題 - 也因此臉書內部才有人開發出 `recoil` 這種完全獨立於 `context` 之外的解決方案並且大受歡迎 - 而 `jotai` 是受 `recoil` 啟發的更精簡、彈性狀態管理工具,因此本次課程採用它 - `jotai` 另外一個強大的優點是它與`react-query`緊密整合而成為一個功能強大的 single source of truth 來管理狀態 # 資料獲取(data-fetching/caching) ## 角色 - 四大功能 - `revalidate`: should the cache revalidate once the asynchronous update resolves. - `optimisticData`: data to immediately update the client cache, usually used in optimistic UI. - `populateCache`: should the result of the remote mutation be written to the cache, - or a function that receives new result and current result as arguments and returns the mutation result. - `rollbackOnError`: should the cache rollback if the remote mutation errors. - 注意上開四功能都是圍繞在底層最基本的 `fetch` 撈資料操作之上運行 - 也就是說`打 API 撈資料`這事反而是最陽春的基本功能隨便用一個 `fetch、axios、ky` 套件做掉就行 - 因此真正要看的重點是資料撈回來後能提供的`加值功能`,例如 `cache、invalidation` 等,當然還有 api 設計的易用性與程式可靠度 ## 坊間工具常見問題是跟 `view` (也就是 `component`)緊密綁定 - 只要是透過 `hooks` 操作的都符合此範圍,例如 `swr` 與 `react-query` - 常見語法是 ```javascript const [data, error] = useSWR('url') ``` - `useSWR()` 就是綁死在 react view 裏面,因為它是個 `hook` - 如此做的缺點是會讓`狀態、資料、畫面`三者緊密耦合(coupling)而難以清楚切分、個別管理,算是巨大的 `anti-pattern` - 以往因採用 `react-context-selector` 管理狀態原本即已跟 `view` (此例中是指 `react context`) 綁定 - 因此順勢採用 `swr` 或 `react-query` 是很合理的選擇,反正所有東西都已綁在 `view` 裏再多一樣也無妨 - 但這樣做的下場就是所有東西混在一起,當程式變大時會難以管理而逐漸失控 - 直到跟 `react-context-selector` 與 `jotai` 作者談過後才驚覺原來現在已有更好的做法,也就是 `jotai` - 這問題直到 `jotai` 出現才有了一次到位的根本解法 - 更棒的是它想出一個優雅方式將 `react-query` 與 `view` 解離 - 能同時管理`狀態與資料`而成為一個個真正的 `single source of truth` ## 選項 # swr - [文件](https://swr.vercel.app/) - 主要優點是背後的開發者`vercel`向來以程式品質優良著稱業界,因此它家寫的東西絕對有信心採用 - 它的另個優點是目前業界流行用 `nextjs` 這個框架以輕鬆實現 `Server-Side Rendering` 等功能,因此跟同公司的 `swr` 非常速配 - 但如果不使用 `nextjs` 的話也就沒必要一定選它了 - 但它的缺點是`too opinionated`也就是客製化可調整的空間很少,大部份時候它都直接提供內建方案解決 - 這樣的設計理念好處是一開始採用時會覺得很順、很開心一下子就能得到結果 - 但久了會發現當需求不同而需要修改它設定時就容易處處碰壁,因為它原本就沒打算要做到非常彈性 - 以 `mutate` 這例子來說它大量採用黑魔法直接做掉 `optimistic update, rollback, revalidate, recatch` 等工作 - 但這也代表它代為決定了許多細節,包含資料結構的格式與 `key` 的彈性 - 因此如果你的使用情境不符合這個條件,或資料結構有特殊需求,可能就不易實現(雖然只要肯花功夫與它打架最終應該也能解決,但平白浪費時間划不來) # react-query - [文件](https://tanstack.com/query/) - 這家前身即為 `react-query` 是業界最知名的 `data-fetching` 工具,只是最近更名為 `tanstack query` - 它的優點在於 api 設計上一開始就保留最大彈性 - 因為它知道每個人的需求不同,因此提供便利的 `onSuccess, onError, onSettled` 等 callback 讓用戶提供自定義 fn 來處理細節 - 除此之外它的功能幾乎與 `swr` 完全相同,甚至可說較晚問市的 `swr` 向它取經借鏡了不少功能,因此視它為 `superset` 也不為過 # jotai/query - [文件](https://github.com/pmndrs/jotai/blob/main/docs/integrations/query.mdx) - 這是 `jotai` 與 `react-query` 整合的套件 - 有了這個 extension 後即可實現真正的 single source of truth,也就是`狀態`全放在 `jotai` 內,也由它負責資料的存取與 cache - 這是眼下最合理也最優雅的解決方案,可從根本上解決許多潛在的麻煩(例如 truth 散落多處難以同步) ## 結論 - 最終決定為了 `jotai` 採用相容的 `jotai/query` 以收強強聯手綜合之效 # 路由器(router) ## 角色 - router 一般只負責三件事 - 畫面佈局(也就是用 routes 表列路徑與其應到的不同 components) - 路徑切換(給入不同 url path 能顯示相應的畫面) - 路徑變數提供(例如 `/user/john/issues/123` 能提供 `john` 與 `123` 這兩段變數) ## 選項 # react-router - [文件](https://reactrouter.com/en/main) - 歷史攸久但經歷六代演化後功能越來越複雜、體積也越來越肥大 - 背後的開發團隊向來以每次出新版就有巨大 breaking change 聞名於業界,讓許多人苦不堪言 - 目前最新的 `v6.4` 版許多設計顯的多餘且越界,例如 `loader` 與 `action` 等功能其實屬於`狀態`與`資料`該處理的領域,而非`router`該代勞的事 - 目前體積約 `640kb` 但能否成功 `treeshaking` 尚待測試 # raviger - [文件](https://github.com/kyeotic/raviger) - 主打以 `hooks` 為主的輕巧 `router`,已開發四年,且積極維護中 - 它完全符合上開 `router` 三項基本需求並且無過多不必要功能 - 目前體積 `104kb` ## 結論 - 在依循`能達成目地之最陽春技術`的原則下會採用`raviger` - 但也已確認它大部份 api 皆與 `react-router` 相容,因此日後可無痛轉換 # react 應用技巧 - TBD # 樣式 - Taiwind - [文件](https://tailwindcss.com/) # 元件管理 - storybook - [文件](https://github.com/storybookjs/storybook) # Design System - TBD # 總結 - 採用 react 開發前端近十年來踩過各種雷與坑後現在已變的較精明知道挑選技術時要觀察哪些細節才能確保走的長遠 - 這次挑選的每個技術都俱備`單純、易用、好掌控、可無痛抽換`的特性 - 例如 `jotai` 出事可換成 `recoil` - 例如 `raviger` 出事可換成 `react-router` - 例如 `react-query` 出事可換成 `swr` - 最極端的例子 `react` 出事可換成 `preact` 不然就直上 `solidjs` - 因此如果有機會在 2022 年的現在開啟一個全新專案(green field project)當然是選當今最優秀的組合 - 簡單來說我對目前挑選的各項技術有充份信心能完成任何 size 的專案開發,並且出事時能快速找到解決方案,可放心採用