經過長時間的等待,自從 React 18 在 2022 年推出以來,React 19 Beta 版終於在近期與開發者見面了。作為 React 18 的下一個主要版本,React 19 帶來了許多讓開發者心動的新特性,這次我們就先來一探究竟有什麼新的功能。
useTransition
在前端操作中,經常會碰到需要更新狀態的情況,例如:使用者點擊按鈕後,需要更新 UI 來響應這個操作。然而,有些狀態更新可能計算量較大或者涉及非同步操作,執行時間較長。
以下是一個範例,當使用者輸入名字後,點擊按鈕,會呼叫 API 更新名字,並且在更新過程中,按鈕會變成不可點擊的狀態。updateNameAPI
這個函式會模擬 API 的行為。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import { useState } from 'react'
const updateNameAPI = async (newName) => { return new Promise((resolve, reject) => { setTimeout(() => { const shouldError = newName.includes('error') if (shouldError) { reject(`名字不能包含"error"`) } else { resolve(`你的新名字是: ${newName}`) } }, 1000) }) }
const Pending = () => { const [name, setName] = useState('') const [error, setError] = useState(null) const [isPending, setIsPending] = useState(false)
const handleSubmit = async () => { setIsPending(true) const error = await updateNameAPI(name) setIsPending(false) if (error) { setError(error) return } }
return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> {isPending ? '更新中...' : '更新名字'} </button> {error && <p style={{ color: 'red' }}>{error}</p>} </div> ) }
|
可以看到,這段程式碼中,我們使用了三個 useState
來管理狀態,並且在 handleSubmit
中,使用 setIsPending
來控制按鈕是否可點擊,以及在 API 回傳錯誤時,使用 setError
來顯示錯誤訊息。
useTransition
這個 hook 已經不是新東西了,它在 React 18 中就已經出現,但在 React 19 中,它被進一步改進,讓我們更容易地處理非同步操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { useState, useTransition } from 'react'
const Pending = () => { const [name, setName] = useState('') const [error, setError] = useState(null) const [isPending, startTransition] = useTransition()
const handleSubmit = () => { startTransition(async () => { // 使用 startTransition 包裹非同步操作 const error = await updateNameAPI(name) if (error) { setError(error) return } }) } // startTransition 會自動處理 pending 狀態 // 在非同步操作期間,isPending 為 true // 操作完成後,isPending 變為 false
return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> {isPending ? '更新中...' : '更新名字'} </button> {error && <p style={{ color: 'red' }}>{error}</p>} </div> ) }
|
這裡我們簡單地使用 useTransition
來取代 setIsPending
,在 useTransition
每次非同步操作時,會自動幫我們處理好 pending 的狀態,讓我們不需要再手動管理。
useActionState
前面展示了 useTransition
已經簡化了一些程式碼,接下來看到 useActionState
這個新 hook,可以更進一步簡化我們的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { useActionState } from 'react'
const UseActionStateExample = () => { const [error, submitAction, isPending] = useActionState( async (state, formData) => { const newName = formData.get('name') const error = await updateNameAPI(newName) if (error) { return error } } )
const handleSubmit = (e) => { e.preventDefault() const formData = new FormData(e.target) submitAction(formData) // 調用 submitAction 提交表單 } // useActionState 自動管理 error 和 isPending 狀態 // 在非同步操作期間,isPending 為 true // 操作完成後,根據返回值更新 error 狀態
return ( <form onSubmit={handleSubmit}> <input name='name' /> <button type='submit' disabled={isPending}> {isPending ? '更新中...' : '更新名字'} </button> {error && <p style={{ color: 'red' }}>{error}</p>} </form> ) }
|
useActionState
分別回傳了三個值,第一個是錯誤訊息,第二個是一個函式,用來提交表單,第三個是一個布林值,用來判斷是否正在執行中。
這個例子中,我們完全不需要 useState
,只需要使用 useActionState
就可以完成前面使用 useTransition
所有的操作,這樣可以讓我們的程式碼更加簡潔。不過眼尖的你,可能也發現到這個例子中,使用的是 form
表單的操作,所以這也表示 useActionState
只能用在表單操作上。
在以前的 React 中,如果按鈕元件獨立出來,要讓按鈕元件知道目前表單是否正在執行中,需要透過 props
傳遞,這樣會讓程式碼變得複雜,不過在 React 19 中,我們可以使用 useFormStatus
來取得表單的狀態:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import { useActionState } from 'react' import { useFormStatus } from 'react-dom'
const SubmitButton = () => { const { pending } = useFormStatus() // 從 useFormStatus 獲取 pending 狀態 return ( <button type='submit' disabled={pending}> {pending ? '更新中...' : '更新名字'} </button> ) }
// useFormStatus 讀取父 <form> 的 pending 狀態 // 無需通過 props 傳遞,降低了組件耦合
const UseFormStatus = () => { const [error, submitAction] = useActionState(async (state, formData) => { const newName = formData.get('name') const error = await updateNameAPI(newName) if (error) { return error } })
const handleSubmit = async (formData) => { submitAction(formData) }
return ( <form action={handleSubmit}> <input name='name' /> <SubmitButton /> {error && <p style={{ color: 'red' }}>{error}</p>} </form> ) }
|
在上面的例子中,示範了使用 useFormStatus
搭配 useActionState
來簡化表單的操作,前者可以不需要傳遞 props
就可以直接幫我們處理運作中的狀態,這樣後者也可以不需要取出 isPending
來判斷是否正在執行中。
useOptimistic
在某些情境下,當使用者提交表單時,我們可以先假設使用者的操作是成功的,然後再等待 API 回傳結果,這樣可以讓使用者感受到更快的回饋,這個概念就是所謂的 Optimistic UI
,在 React 19 中,我們可以使用 useOptimistic
來實現這個功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { useState } from 'react' import { useOptimistic } from 'react'
const OptimisticExample = () => { const [title, setTitle] = useState('React 18') const [optimisticTitle, setOptimisticTitle] = useOptimistic(title)
const submitForm = async () => { const newTitle = 'React 19' setOptimisticTitle(newTitle) // 立即渲染 optimistic 狀態 const res = await new Promise((resolve) => { setTimeout(() => { resolve(`${newTitle} Beta`) }, 1000) }) setTitle(res) // 更新為最終結果 } // useOptimistic 使得我們可以優雅地實現 optimistic UI // 先展示期望的最終狀態,等待非同步操作完成後再更新為實際結果
return ( <form action={submitForm}> <h1>{optimisticTitle}</h1> <button type='submit'>更新</button> </form> ) }
|
在這個例子中,我們使用 useOptimistic
來取代 useState
,這樣就可以讓我們在提交表單時,先更新 UI,然後再等待 API 回傳結果,這樣可以讓使用者感受到更快的回饋。
use
在之前版本的 React 中,如果想讀取一些外部的 API 資料,一般都會使用 useEffect
來處理,可以先看以下的範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import { useState, useEffect } from 'react'
const Messages = ({ messages }) => { return ( <ul> {messages.map((message) => ( <li key={message.id}> <h2>{message.title}</h2> <p>{message.body}</p> </li> ))} </ul> ) }
function FetchExample() { const [messages, setMessages] = useState([]) const [loading, setLoading] = useState(true)
useEffect(() => { const fetchData = async () => { try { const res = await fetch('https://jsonplaceholder.typicode.com/posts') const data = await res.json() setMessages(data) setLoading(false) } catch (err) { console.error(err) } finally { setLoading(false) } }
fetchData() }, [])
if (loading) { return <p>⌛Downloading message...</p> }
return <Messages messages={messages} /> }
|
在 React 19 中,我們可以使用 use
來取代 useEffect
,這樣可以讓我們的程式碼更加簡潔:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { use, Suspense } from 'react'
const fetchData = async () => { const res = await fetch('https://jsonplaceholder.typicode.com/posts') return res.json() }
const Messages = () => { const messages = use(fetchData()) // use 讀取非同步資源,並暫停渲染直到完成
return ( <ul> {messages.map((message) => ( <li key={message.id}> <h2>{message.title}</h2> <p>{message.body}</p> </li> ))} </ul> ) }
// use 簡化了非同步讀取資源的實現 // 不需要使用 useEffect 提高可讀性
function UseExample() { return ( <Suspense fallback={<p>⌛Downloading message...</p>}> <Messages /> </Suspense> ) }
|
Context
最後來介紹 React 19 的 Context,這個部分算是有點小變動,以往在使用 Context 的時候,需要加上一個 Provider
,像是 <Context.Provider>
。
但在 React 19 中,我們可以直接使用 Context
來取代 Provider
,這樣可以讓我們的程式碼更加簡潔:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { createContext, useContext } from 'react'
// 新建 Context const ThemeContext = createContext('light')
function ThemeProvider({ children }) { const theme = 'light'
// 使用新語法渲染 <Context> 作為 provider return <ThemeContext value={theme}>{children}</ThemeContext> }
function ThemedButton() { // 使用 Context const theme = useContext(ThemeContext)
return ( <button style={{ backgroundColor: theme === 'light' ? 'white' : 'black', color: theme === 'light' ? 'black' : 'white' }} > I am a {theme} themed button </button> ) }
const ContextExample = () => { return ( <ThemeProvider> <ThemedButton /> </ThemeProvider> ) }
|
在這個例子中,當傳遞 value
為 light
時,ThemedButton
會顯示一個白色的按鈕,當傳遞 value
為 dark
時,ThemedButton
會顯示一個黑色的按鈕。
結論
React 19 Beta 還有其他功能,這篇文章只是挑出一些常用的情境可使用的 hook 和 API 來介紹,想了解更多的話,在 React 19 正式版推出再來做更加詳細的介紹。
總的來說,React 19 Beta 為開發者帶來了許多改變,讓非同步渲染、數據獲取和更新的體驗獲得極大提升。雖然這只是一個 Beta 版本,但這些新功能和改進都值得我們熱烈期待 React 19 的正式到來。最後讓我們拭目以待,並為即將到來的 React 開發新紀元做好準備吧!