React 18 登場 ! 新增功能大簡介
2022-04-01
筆者不是經常會寫關於React的文章,對上一篇寫關於React
的文章已是2019年講關於以React Hooks來編寫函數式部件(Function Component)的文章,
因為在筆者看來,React
自2013年推出以來,API 已經非常穩定,近年開發重點主要放在改善效能以及改善開發者體驗(DX, Developer experience)之中。 React
17 更罕見在官網之中,際出一句No new features
,殊不知這一切只是為了React 18
舖路。
React
18 可說是自React
16.8 推出 React Hooks
兩年多以來最大變動,其中主要變動都離不開兩個字 ⸻ 並發(Concurrency)。
也就是React
18的主要功能,都為了改善React
在並發編程方程之效能支援,以及改善開發難度而設的。
Suspense
Suspense
在 React
17 早已出現,但一直處於experimental
,而非stable
。React
團隊也一早表明React
17是一個過渡性的更新,不會有
大變動。自然而言,這個大變動,在React
18 就名正言順的推出,更與現今相當流行的方法伺服器端渲染(Server Side Rendering)相容。
筆者在此舉一個例子,展示Suspense
的用途,例子當然是用筆者最愛的TodoList
export function TodoList() { // 1. 先定義 State const [todos, setTodos ] = useState<TodoList>([]) // 3. 用fetch去server 讀取數據 useEffect(()=>{ async function fetchTodos(){ const res = await fetch('/todos') const todosArr = await res.json() setTodos(todosArr) } fetchTodos() },[setTodos]) // 2. 再定義要 render 部件的HTML return ( <div> <h2>Todo List</h2> <div> {todos.map(todo => ( <div key={todo.id}> <div>#{todo.id}</div> <div>{todo.title}</div> <div> <Link to={`/todo-detail/${todo.id}`}> <Button> show details </Button> </Link> </div> </div> ))} </div> </div> ) }
這是一個典型函數式部件(Function component)的例子,有三大步驟:
- 先定義 Todo array 的State
- Render 部件的HTML
- 以Fetch 去讀取server的數據
由於從Server讀取數據需時而且是非同步的,在步驟2
與3
之間,有一段短時間,是只有HTML,沒有數據的。
這種方法,在React
官方影片中被稱為Render-then-fetch
,因為確是先render
然後再fetch
,這種做法,對於React 老手這種寫法當然稀鬆平常,但隨著部件的複雜程度愈高,理解就會愈來愈困難,主因在於是非同步編程(Asynchronous Programming),理解方面始終比同步編程(Synchronous Programming)較為複雜。
Suspense
希望解決的問題,就是為了將這種非同步編程,變成為簡化的同步編程。
只要將 TodoList
部件,包含在Suspense
部件之中
// In App.tsx export function App(){ return ( <Suspense fallback={<Loading />}> <TodoList /> </Suspense> ) } // In TodoList.tsx export function TodoList(){ // 這裏可以使用一個名為useFetch的custom hooks,簡化程式碼 // 1. 從Server讀取數據, const { data: todos = [] } = useFetch('/todos', { suspense: true // can put it in 2 places. Here or in Provider }, []) // 2. 假如數據未就緒,就顯Loading部件 //3. Render要顯示的HTML return ( <div> <h2>Todo List</h2> <div> {todos.map(todo => ( <div key={todo.id}> <div>#{todo.id}</div> <div>{todo.title}</div> <div> <Link to={`/todo-detail/${todo.id}`}> <Button> show details </Button> </Link> </div> </div> ))} </div> </div> ) }
整個結構簡單不少,因為少了useEffect
的非同步編程,結果令整段程式碼更容易理解。
Suspense
運作起上來,有點像try-catch
與loading
的混合。也就是當數據未完成,就只fallback 至<Loading/>
,完成後,就顯
示內容。
除了client-side rendering
以外,Suspense
也能同時應用在server-side rendering
之上。
在這個討論之中,解釋了Suspense
在改善SSR上的重要性。
假如我們直使用SSR
,那麼Server就會等齊整個<Layout/>
內的每一個部件就緒,才會將HTML
送到瀏覽器。
// Client side <Layout> <NavBar /> <Sidebar /> <RightPane> <Post /> <Comments /> </RightPane> </Layout>
如果我們用Suspense
包含如下圖的<Comments/>
,在React
18 起,React 就會先將<Comments/>
以外的HTML
都送到前端,而在Comments 的位置,就只會顯示一個<Spinner />
以提示用戶,Comments
正在載入。
// Client side <Layout> <NavBar /> <Sidebar /> <RightPane> <Post /> <Suspense fallback={<Spinner />}> <Comments /> </Suspense> </RightPane> </Layout>
其他功能 : Transition
另一個React
18新加的功能,就是Transition
的 API, 作用在於將State
新
分為緊急(Urgent)及非緊急(Non-urgent)兩種,Transition
對應的也就是非緊急的state
更新。
用戶按制、輸入文字等,必須為緊急更新(Urgent update),否則用戶很容易會覺得畫面無反應。
Transition
則包含像顯示文字的State
更新,這些更新縱有少少延遲,也不會影響用戶體驗。
Transition
API 有一個React Hooks
,名為 useTransition()
。
const [isPending, startTransition] = useTransition() // isPending用來表示該Transition 是否已經完結。 startTransition(()=>{ // 顯示Todo 內容的狀態更新不是緊急至必須馬上有反應。 setTodos(todos); })
其他功能: Automatic Batching
Automatic Batching
則是React 為了改善效能而做的。
大家如果寫過React,都知道 狀態更新setXXX
不一定是馬上執行,因為React會將多個setXXX
合成一個去處理。 也就是說以下的例子中,count
會是1 不是2 。
因為React 會將這兩個更新合為一個,也就是只剩下後面的count+1
export function Counter(){ const [count, setCount] = useState(0) const handleClick = ()=>{ setCount(count + 1) setCount(count + 1) // Automatic batching } return ( <div> <button onClick={handleClick}> </div> ) }
但在React
18以前,Automatic batching
只會在event listener
中運作,
setTimeout
,fetch
等動作之後的狀態更新,是不會執行automatic batching
的。
React
18的更新,就是將所有狀態更新都會執行automatic batching
。因此以下例子在React
18中,
count
依然會是1
,而且只會re-render
一次。
export function Counter(){ const [count, setCount] = useState(0) const handleClick = ()=>{ async function callServer(){ const res = await fetch('/count') const result = await res.json() setCount(count + 1) setCount(count + 1) // Automatic batching } } return ( <div> <button onClick={handleClick}> </div> ) }
嘗試階段: Server Component
React
18還有一個尚在嘗試階段的功能,就是Server Component
。乍聽之下與Server Side Rendering
有些相似,事實上兩者截然不同。
Server Side Rendering
是指在Server
先產生好HTML ,再到Client
產生須要與用戶互動的部份Server Component
是指完全在Server
產生HTML的做法,是React
18 的新功能,完全無須client
的JavaScript
最受歡迎的React
框架Next.js
,就有一個例子,專為React
18的Server Component
而設。
// pages/home.server.js import { Suspense } from 'react' import Profile from '../components/profile.server' import Content from '../components/content.client' export default function Home() { return ( <div> <h1>Welcome to React Server Components</h1> <Suspense fallback={'Loading...'}> <Profile /> </Suspense> <Content /> </div> ) }
總結
React雖然早已在前端開發中獨佔鰲頭,領先的地位並沒有令React
停止改進,
React
18確實使前端開發者又進一步,在效能上及開發難度上都改善不少,
實在是筆者這些恆常React
開發者的福音啊。
留言
閱讀更多
到底React Hooks 有何特別?
2018-11-27
新近推出的React 16.7包括一個很有趣的功能,名字叫做React Hooks。看到這個名字,很多人會下意識認為是在講componentDidMount, componentDidUpdate等方法。但其實這些方法的正名是 React Lifecycle Method, 推出React Hooks是為了方便開發者多用functional component,但仍然能夠使用state及 props等重要功能。
到底React Hooks有何特別(二)?淺談useEffect及useReducer
2018-11-29
於本篇文章的上集,我們討論了useState如何令Stateful React Component簡化良多,此篇主要討論的是如何使 用useEffect。useEffect可以簡化state,很多人都提到React Hooks有可能可以完全取代Redux作為 React State Management的標準,正因如此。
React Hooks(三):Redux-React-Hook
2019-03-27
React Hooks在React 16.8.0的版本正式成為React的正式功能。正如前兩篇所言,React Hooks簡化了寫複雜代碼的難度,亦令React的函數式部件(Functional Components)亦能使用state及props,可是傳說中React Hooks將會取代Redux呢?卻一直都是只聞樓梯響。這篇文章就會介紹一個筆者認為頗有前景的組件,就是在Github中的facebookincubator中的redux-react-hook。
React Hooks(四):全函數式React
2019-09-09
筆者在上年十一月React Hooks剛發佈時,就寫過關於React Hooks的應用,如何簡化開發React 應用時要寫的程式碼,之後又介紹了Redux-React-Hooks這個筆者認為有不錯前途的組件,雖然隨著React-Redux加入了React Hooks的應用,現在寫React + Redux應用,已無需再寫長長的mapStateToProps及mapDispatchToProps。
Tec。士多開發日記系列:第三篇:實作Tec記士多Frontend(GraphQL Client入門)- Part 1
2022-02-21
上一編Blog已經簡單講解如何實作GraphQL Server。今編Blog會深入講解Frontend如何同GraphQL Server進行溝通。Frontend最常做兩類GraphQL Query Statement,查詢(Query)及 變更(Mutation)。還有其他類型,不過坊間仍未普遍使用,所以在此不作介紹。
Tec。士多開發日記系列:第三篇:實作Tec記士多Frontend(GraphQL Client入門)- Part 2
2022-02-22
上集已經詳細講解如何寫Query/Mutation Statement及如何是Apollo GraphQL提供的Playground做測試。下一步就是請解如何是Client Side Application(以React Application為例)中使用Query/Mutation Statement。我會分開兩個版本示範,實作GraphQL Client (基本版)及GraphQL Client (應用版 - Apollo Client)。