筆者不是經常會寫關於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
在 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)的例子,有三大步驟:
由於從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>
另一個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
則是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> ) }
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 16.7包括一個很有趣的功能,名字叫做React Hooks。看到這個名字,很多人會下意識認為是在講componentDidMount, componentDidUpdate等方法。但其實這些方法的正名是 React Lifecycle Method, 推出React Hooks是為了方便開發者多用functional component,但仍然能夠使用state及 props等重要功能。
於本篇文章的上集,我們討論了useState如何令Stateful React Component簡化良多,此篇主要討論的是如何使 用useEffect。useEffect可以簡化state,很多人都提到React Hooks有可能可以完全取代Redux作為 React State Management的標準,正因如此。
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 Hooks的應用,如何簡化開發React 應用時要寫的程式碼,之後又介紹了Redux-React-Hooks這個筆者認為有不錯前途的組件,雖然隨著React-Redux加入了React Hooks的應用,現在寫React + Redux應用,已無需再寫長長的mapStateToProps及mapDispatchToProps。
上一編Blog已經簡單講解如何實作GraphQL Server。今編Blog會深入講解Frontend如何同GraphQL Server進行溝通。Frontend最常做兩類GraphQL Query Statement,查詢(Query)及 變更(Mutation)。還有其他類型,不過坊間仍未普遍使用,所以在此不作介紹。
上集已經詳細講解如何寫Query/Mutation Statement及如何是Apollo GraphQL提供的Playground做測試。下一步就是請解如何是Client Side Application(以React Application為例)中使用Query/Mutation Statement。我會分開兩個版本示範,實作GraphQL Client (基本版)及GraphQL Client (應用版 - Apollo Client)。