avatar
instagramthreads
Published on

演講筆記|Reactjs.tw - 後 React 18 時代的技術選型

Authors

這篇文章來筆記一下看完 Reactjs.tw 社群小聚直播的筆記,這場演講中討論關於 React 18 的生態系近期發展與技術選型議題。

Intro

什麼是後 React 18 時代

  • 上次 React 正式發佈時間是 2022.6 月中的 18.2.0,對主流框架來說並不常見,連 minor 與 patch version 都沒更新
  • 最新的版本主要由 Vercel 成員去發佈(查了一下有蠻多 PR 是由這位 Josh Story 貢獻)
  • 正式版雖然沒有發,但發佈了許多 Canary release
  • 許多 React core team 成員如 Andrew ClarkSebastian已經離開去了 Vercel;而 Dan 去了 BlueSky
  • BTW,原來 Vercel 唸法音近於 Ver-Sell,我一直都念錯成 Ver-So
  • Kent Dodds 的推特投票講述 ‘use client‘ 是屬於 RSC 的功能,但目前許多人誤認為是 Next.js 裡的功能
    • 發現 Kent Dodds 推特有轉貼另一個框架統計,也是由 Next.js 勝出,不過有另一派人也提到了 Nuxt.js

React 生態系近期重大發展

Concurrent Rendering

React 18 新特性,可利用 useTransition 去區分「更新任務」的執行優先順序,讓畫面更新不要被卡住

看文件沒看很懂,看了這篇有實例的文章蠻好懂的,以下筆記一下我的理解:

  • transition 可理解為過渡階段,也就是可以把某些比較 heavy 的更新任務標記為「低優先序」,而其他沒被標記的可能會是比較輕量的緊急更新任務
  • 舉個例子,今天你有個搜尋 input 框,以及一個名片列表,你需要實作一個用關鍵字篩選去更新符合條件的名片列表,一般版本會這樣寫:
const [searchTerm, setSearchTerm] = useState('');
const [filtered, setFiltered] = useState(users);

const handleChange = ({ target: { value } }) => {
  setSearchTerm(value);
  setFiltered(users.filter((item) => item.name.includes(value)));
};
  • 在 input 框的 onChange 事件中去觸發 handleChange 來產生兩組更新任務:「更新搜尋關鍵字」及「更新篩選出來的名片列表」。
  • 問題來了,每當你去打關鍵字時就會同時一直同時去更新名片列表可能會造成打字起來頓頓的,這時 useTransition 就派上用場了:
const [searchTerm, setSearchTerm] = useState('');
const [filteredUsers, setFilteredUsers] = useState(users);
const [isPending, startTransition] = useTransition();

const handleChange = ({ target: { value } }) => {
  setSearchTerm(value);
  startTransition(() => {
    setFilteredUsers(users.filter((item) => item.name.includes(value)));
  });
};

return <>{isPending ? <span>Loading...</span> : <UserList users={filteredUsers} />}</>;
  • 從上面例子可以看到,startTransition 的 callback 裡可以去執行你要排入「低優先序」的更新任務到 transition 模式;而這個 isPending 的狀態就是當今天在執行 startTransition 時會為 true。
  • 如此,就可以確保你的 setSearchTerm 的更新任務不會被卡住,使用起來可以更順暢,其實某種程度上感覺跟 debounce 的概念有點類似
  • 進階一點,影片中講者有提到這個 concurrent mode 其實在 state management 的情境會蠻好用,可以更好地去區分哪些更新狀態的任務可能暫時較不重要,因為這些眾多較緊急的更新任務可能同時會需要觸發某個 heavy updating task 的更動,所以可以避免一些無謂的效能浪費或 race condition。實例可能也可以再深入研究一下。

Streaming Rendering

  • 可以參考 Dan 的這篇 New Suspense SSR Architecture in React 18 講解,或看到也有人已經有翻譯成中文筆記(搭配圖會更好理解)
  • 簡單來說就是以前的 SSR 是一個 waterfall 的流程:
    • 在 server 端拉取資料後,準備好整份靜態 HTML 回傳
    • 在 client 端載入這份 HTML 後,再與 React runtime 做 hydration(把 JS 邏輯與 HTML 水合在一起)
  • 而有了 streaming rendering 與 Suspense 之後:
    • 可以選擇把可能需要花比較久的 component 先用 Suspense 包起來
    • 讓一些需要優先被渲染出來的元件可以在第一時間送到使用者面前並且可以操作
    • 而這些被 Suspense 包起來的部分一開始可以先被一些 placeholder 佔位(skeleton loading、spinner 等等)
    • 等到其他核心元件 hydration 完成後,剩餘這些 Suspense 的部分再透過 streaming 的方式從 server 傳過來 client 做剩餘的 hydration
    • 如此一來就能優化 SEO 及 web vital 分數

Async Server Component

  • 在 2020 時 React 提出這個 server component 這個概念,但如今在 2022 年 Next.js 13 推出支援 RSC 後,目前已經算是個必知的知識點
  • React Server Component (RSC) 的運作方式:
    • 可以想像原本以往我們有一個 component tree 都是 client component。
    • 在有了 RSC 後,樹上的節點有些就可以只放在 server 端處理
    • 好處是可以減少 client bundle size,因為部分 package 因為是 server component 的關係,就不需要被打包到 client side
  • 但 RSC 的一個影響是部分 library 可能需要再去支援這類的生態。因為 RSC 的一個限制就是沒辦法用諸如 useContext、useEffect 等這些 client side 互動的 hook,就會需要再去拆分元件、寫 use client 等等的處理
  • 另外像是 styled component 這樣的 CSS-in-JS 也是不能在 RSC 裡做使用的,這也可能會是部分專案的一個痛點

Server Actions

  • 熱騰騰的在 2023 的 10 月底剛與 Next.js 14 共同推出的 React Canary release,而 Next.js 14 表示 Server Actions 已是 stable 功能
  • Server Actions 可以讓開發者在 component 內透過 use server 來去操作資料庫
  • 從 Next.js Conf 2023 看起來確實是為了部分使用全端框架的開發者來說增進了不少開發體驗,可以寫更少的 code
  • 但講者認為有許多安全性的隱性成本需要注意,方便會帶來其他的代價。另外社群也討論到,看似走 PHP 的回頭路,但認為也算是一種創新,改善了當年不方便的地方,再次提出一種嘗試。

其他諸多實驗性質且難懂的 API

  • use
  • useEffectEvent
  • useFormState
  • useFormStatus
  • useOptimistic
  • useMemoCache
  • taintUniqueValue
  • taintObjectReference
  • SuspenseList

另外還有那個 React Forget 什麼時候會出… (ref)

技術選型

框架

React 官方建議我們使用框架,全端框架的部分:

  • Next.js
    • 較主流且更多下載量,生態系豐富
    • 與 React core team 合作密切,逐步實踐 React 願景
    • 缺點:升版容易壞,常使用 React Canary
  • Remix
    • 由 React Router 團隊打造
    • 技術選型上,考量目前下載量太低,未來假如專案遇到問題可能不好解決
    • 跟進 RSC 進度慢,且人緣差,比較沒得到 React Core Team 支持
  • 社群討論
    • 提到十月底時 Remix 社群的 Kent Dodds 與 Vercel DX VP Lee 的 To use or not to use Next.js 的討論:
    • 以及也引述了 Dan 更早之前在這則推文中的討論後:「Next.js 願意 rewrite 整個框架原本架構來去實現 React Team 的 vision,就像是 RSC、App Router 概念一直到 Next.js 13 上才得以被實踐」,從第三方觀點可更放心 Vercel 是走在好的方向

Styling

  • 區別
    • Zero-Runtime (或稱 Build time),指有先做 pre-build 成 CSS 的 styling library,如:
      • Tailwind (官方最推薦)
      • CSS Module (官方最推薦)
      • StyleX
        • 由 Meta 開發,似乎有被用在 IG 與 Threads 的網頁版,前陣子似乎有開源計畫,目前文件也有初步釋出,但目前看 github repo 是關掉的狀態,看來還沒準備完全
        • 提到前陣子一個有名的前端 KOL Theo 有拍了一支影片在討論 StyleX
      • Linaria
      • Panda CSS
      • vanilla-extract
    • Runtime,指在 JS runtime 時才動態計算出對應 CSS,這些將會在 RSC 時代過得比較辛苦:
  • 開發時有時也沒辦法在一開始就很確定某個元件會是 server component 或是 client component,可能會常常利用 use clientuse server 去調動邊界,所以更應考量比較 universal 的 styling solution

表單處理

  • react-hook-form:下載量較多

  • formik

  • 另順便介紹前面提到的實驗性 hook:

    • useFormState
    • useFormStatus
    • useOptimistic:例子像是在 Facebook 上留言後會直接出現在你的 UI 上的用途,那如果後來這個更新真的有 fail 會另外再顯示傳送失敗的紅字提示等。概念是會即時地更新畫面上看到效果,才去做資料更新與寫入。
  • 但以上提到三個 hook 用途目前看起來都不大,一方面是有其他 library 已經可以做到,另一方面是沒有提供表單驗證相關的功能

Data Fetching

  • react-query
    • 最多人使用,現改名為 tanstack-query,為了 framework agnostic (框架不可知論,為了消弭這個 library 只能用在特定框架的迷思)
    • 推薦文章,react query 核心開發者 TkDodo 近期有寫過兩篇文章:
  • Redux:雖然目前仍是下載量最多,但已經停止成長一陣子了,猜測只是舊專案改不動還沒走,且連 Redux 作者 Dan 也提過不太建議再使用 Redux (ref)
  • 其他
    • Server Component 可直接用 fetch
    • Client Component 可直接用 use() 等 promise

State management

  • ZustandJotaiValtio
    • 這三者皆為同一個作者 Daishi 開發,是個多產的日本大神 🤯
    • 前兩套的選擇可以參考文件這一段
      • 選 Jotai 的原因
        • 想尋找一個取代 useContext、useState
        • code split 考量
        • 如果想使用 Suspense
      • 選 Zustand 的原因
        • 如果想要 simple module state
        • 如果 Redux devtiils 對你很重要
    • 另 Daishi 有幾個 repo 在談這些狀態管理工具的比較:
    • 目前筆者開發上是選擇使用 Zustand,用起來輕鬆簡單,像極了當年在寫 Vue 時的 Vuex 與現在的 Pinia,另外兩套還未多有著墨,未來有興趣可以再來研究
  • Recoil
    • 由 Meta 團隊開發
  • Redux:同上述,已較少新專案會考慮使用

整合測試

  • react-testing-library
    • 大家最常用的整合測試工具,目前在 RSC 尚無解 (ref issue),討論結論有一些 workaround:
      • 參考討論裡面最佳解,對 RSC 包一層,等待它 run 完輸出為 client component 後去測試其行為
      • 需 mock server-only (ref)
    • Canary 版號問題,因為 Next.js 裡有用上一些 React 18.3.0-canary 的版本,若版本沒對齊可能過不了
    • use server 的 directive 會被忽略,因為整合測試還沒過 bundler,所以也可能測不出實際行為
    • 講者提到觀察目前尚無 React Team、Next.js Team 的人加入討論,所以暫時可能沒有最佳解

E2E 測試

  • 整合測試中的限制可以交由 E2E 來做,可盡可能模擬使用者行為
  • 講者最推薦使用 Playwright,因認為是在 JS 領域最突出且可使用其他語言去寫
    • 且目前 Next.js 似乎有開發者在著手實作相關 helper (ref discussionPR)
  • 這裡因為筆者經驗是只有用過另一套 Cypress,兩者的比較或許未來也可以另外再研究看看,目前從 npm trends 看的結果如此連結

總結與 Q&A 筆記

  • 總結

    • 技術選型很難,有時連 library 作者都不知道怎辦,若不急的話可以再等等,用時間換取正確決定,多研究是好事
    • 若想用上 RSC 不用框架是很難的一件事,不是一個團隊就能考量全面的事,想想 Next.js 中有數千位貢獻者
    • 有機會可以將 runtime CSS-in-JS solution 換掉,未來性較低,效能也較差、bundle size 大
    • 多寫 E2E 測試增加產品穩定性
    • 若專案沒有效能、bundle size 問題,client component 還是很夠用的
  • Q&A

    • Q. 以 Dcard 來說有用上 Next.js App Router 了嗎
      • A. 因可區分為多個專案,可分為外部、內部或者偏靜態網站或動態的,目前有些小型專案有在實驗。雖然主站要改起來肯定是沒那麼容易,但會尋找漸進式的方式去做,因為那才是能看到最大化改善的地方。以講者個人而言,若要開新專案,肯定是會選擇使用 App Router,但要用多少 use client 會是每個人的選擇,且目前有些 library 尚未到位,還無法完全支援 RSC。
    • Q. 提到之前 DHH 不使用 TypeScript 的討論,講者怎麼看 TypeScript 的使用?
      • A. 個人會用,認為在團隊合作時寫 TS 有許多好處,像是自動完成、可以馬上看懂某個複雜的資料內含什麼屬性等,認為不一定需要用到很艱澀的 TS 功能,但基本的型別是方便的。但若是開發上有遇到較難的型別錯誤修不好,也是可考慮 disable 掉或用 any、JS 等方式。但 Dcard 中有遇到一個問題是在大型專案上 TS 的 build time 太久,認為是目前唯一的痛點。
    • Q. 認為 Next.js 近期發展讓開發者門檻上升不少,好奇就「講者的個人經驗」而言,這些改動,實際上有替「使用者或產品」帶來什麼有感的價值嗎?
      • A. 肯定有啊,效能跑分很好看及 SEO 效果佳。最大理由就是效能考量,像是 RSC 讓 bundle size 降低、Streaming rendering 能讓使用者更快與網頁互動。認為難是對於習慣原本 pattern 的人,若白紙來學可能對這些黑魔法感到方便。

後記

看到社群小聚主辦人在 Facebook 社團有貼出報名議程的資訊,有點小心動,但自認目前手邊研究的內容還上不了檯面,可能要再繼續囤積一陣子再說,也分享個資訊:

看完也有想跟大家分享的主題嗎? 請參考小聚講者報名連結:https://forms.gle/fea9qK2H8EM935TU8

參考資料