React 面试题详细答案 - 第 38 题
38. React 18 有哪些重要更新?(自动批处理、并发特性、新的 Root API)
React 18 概述
React 18 是 React 的一个重要版本,引入了许多新特性和改进,主要围绕并发渲染、自动批处理和新的 Root API 展开。
1. 自动批处理 (Automatic Batching)
什么是批处理?
批处理是指 React 将多个状态更新合并为一次重新渲染,以提高性能。
// React 17 及之前:只有 React 事件处理器中的更新会被批处理
function handleClick() {
setCount((c) => c + 1)
setFlag((f) => !f)
// React 17:只会重新渲染一次
}
// React 17:Promise、setTimeout、原生事件处理器中的更新不会被批处理
setTimeout(() => {
setCount((c) => c + 1)
setFlag((f) => !f)
// React 17:会重新渲染两次
}, 1000)
fetch('/api/data').then(() => {
setCount((c) => c + 1)
setFlag((f) => !f)
// React 17:会重新渲染两次
})
React 18 的自动批处理
// React 18:所有更新都会被自动批处理
function handleClick() {
setCount((c) => c + 1)
setFlag((f) => !f)
// React 18:只会重新渲染一次
}
setTimeout(() => {
setCount((c) => c + 1)
setFlag((f) => !f)
// React 18:只会重新渲染一次
}, 1000)
fetch('/api/data').then(() => {
setCount((c) => c + 1)
setFlag((f) => !f)
// React 18:只会重新渲染一次
})
// 如果需要立即更新,可以使用 flushSync
import { flushSync } from 'react-dom'
function handleClick() {
flushSync(() => {
setCount((c) => c + 1)
})
// 这里 count 已经更新
flushSync(() => {
setFlag((f) => !f)
})
// 这里 flag 已经更新
}
2. 并发特性 (Concurrent Features)
startTransition
startTransition 用于标记非紧急的更新,让 React 知道这些更新可以被中断。
import { startTransition, useState } from 'react'
function SearchResults() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, setIsPending] = useState(false)
const handleSearch = (newQuery) => {
setQuery(newQuery) // 紧急更新:立即更新输入框
startTransition(() => {
setIsPending(true)
setResults([]) // 非紧急更新:清空结果
// 模拟搜索
searchAPI(newQuery).then((searchResults) => {
setResults(searchResults)
setIsPending(false)
})
})
}
return (
<div>
<input value={query} onChange={(e) => handleSearch(e.target.value)} />
{isPending && <div>搜索中...</div>}
<div>
{results.map((result) => (
<div key={result.id}>{result.title}</div>
))}
</div>
</div>
)
}
useDeferredValue
useDeferredValue 用于延迟更新某个值,让其他更紧急的更新优先执行。
import { useDeferredValue, useState, useMemo } from 'react'
function ProductList({ products }) {
const [filter, setFilter] = useState('')
const deferredFilter = useDeferredValue(filter)
const filteredProducts = useMemo(() => {
return products.filter((product) =>
product.name.toLowerCase().includes(deferredFilter.toLowerCase())
)
}, [products, deferredFilter])
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="搜索产品..."
/>
<div>
{filteredProducts.map((product) => (
<div key={product.id}>{product.name}</div>
))}
</div>
</div>
)
}
Suspense 改进
React 18 改进了 Suspense 的行为,支持更多的使用场景。
import { Suspense, lazy } from 'react'
// 懒加载组件
const LazyComponent = lazy(() => import('./LazyComponent'))
function App() {
return (
<div>
<h1>我的应用</h1>
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
</div>
)
}
// 数据获取的 Suspense
function UserProfile({ userId }) {
const user = use(fetchUser(userId)) // 假设 use 是一个数据获取 Hook
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
}
function App() {
return (
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile userId="123" />
</Suspense>
)
}
3. 新的 Root API
createRoot
React 18 引入了新的 createRoot API 来替代 ReactDOM.render。
// React 17 及之前
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
// React 18
import { createRoot } from 'react-dom/client'
import App from './App'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(<App />)
// 卸载应用
root.unmount()
hydrateRoot
用于服务端渲染的客户端水合。
// React 17
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.hydrate(<App />, document.getElementById('root'))
// React 18
import { hydrateRoot } from 'react-dom/client'
import App from './App'
const container = document.getElementById('root')
const root = hydrateRoot(container, <App />)
4. 新的 Hooks
useId
useId 用于生成唯一的 ID,特别适用于可访问性属性。
import { useId } from 'react'
function Form() {
const nameId = useId()
const emailId = useId()
return (
<form>
<label htmlFor={nameId}>姓名</label>
<input id={nameId} type="text" />
<label htmlFor={emailId}>邮箱</label>
<input id={emailId} type="email" />
</form>
)
}
// 在服务端和客户端生成相同的 ID
function Checkbox() {
const id = useId()
return (
<>
<input id={id} type="checkbox" />
<label htmlFor={id}>同意条款</label>
</>
)
}
useSyncExternalStore
useSyncExternalStore 用于订阅外部数据源,确保在并发渲染时数据的一致性。
import { useSyncExternalStore } from 'react'
function useOnlineStatus() {
return useSyncExternalStore(
// 订阅函数
(callback) => {
window.addEventListener('online', callback)
window.addEventListener('offline', callback)
return () => {
window.removeEventListener('online', callback)
window.removeEventListener('offline', callback)
}
},
// 获取快照函数
() => navigator.onLine,
// 服务端快照函数
() => true
)
}
function App() {
const isOnline = useOnlineStatus()
return (
<div>
<h1>{isOnline ? '在线' : '离线'}</h1>
</div>
)
}
useInsertionEffect
useInsertionEffect 用于在 DOM 更新之前插入样式,主要用于 CSS-in-JS 库。
import { useInsertionEffect } from 'react'
function useCSS(rule) {
useInsertionEffect(() => {
const style = document.createElement('style')
style.textContent = rule
document.head.appendChild(style)
return () => {
document.head.removeChild(style)
}
})
}
function MyComponent() {
useCSS(`
.my-component {
color: red;
font-size: 16px;
}
`)
return <div className="my-component">Hello World</div>
}
5. 严格模式改进
React 18 的严格模式会故意双重调用组件、Effect 和状态更新函数,以帮助发现副作用。
import { StrictMode } from 'react'
function App() {
return (
<StrictMode>
<MyComponent />
</StrictMode>
)
}
// 在严格模式下,以下函数会被调用两次
function MyComponent() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('Effect 运行') // 会被调用两次
}, [])
const handleClick = () => {
setCount((c) => {
console.log('状态更新') // 会被调用两次
return c + 1
})
}
return <button onClick={handleClick}>Count: {count}</button>
}
6. 实际应用示例
性能优化示例
import { startTransition, useDeferredValue, useState, useMemo } from 'react'
function SearchApp() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, setIsPending] = useState(false)
const deferredQuery = useDeferredValue(query)
const filteredResults = useMemo(() => {
if (!deferredQuery) return []
return results.filter((result) =>
result.title.toLowerCase().includes(deferredQuery.toLowerCase())
)
}, [results, deferredQuery])
const handleSearch = (newQuery) => {
setQuery(newQuery) // 立即更新输入框
startTransition(() => {
setIsPending(true)
// 模拟 API 调用
fetch(`/api/search?q=${newQuery}`)
.then((res) => res.json())
.then((data) => {
setResults(data)
setIsPending(false)
})
})
}
return (
<div>
<input
value={query}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索..."
/>
{isPending && <div>搜索中...</div>}
<div>
{filteredResults.map((result) => (
<div key={result.id}>{result.title}</div>
))}
</div>
</div>
)
}
并发渲染示例
import { startTransition, useState, useTransition } from 'react'
function TabContainer() {
const [tab, setTab] = useState('home')
const [isPending, startTransition] = useTransition()
const handleTabChange = (newTab) => {
startTransition(() => {
setTab(newTab)
})
}
return (
<div>
<div>
<button
onClick={() => handleTabChange('home')}
style={{ opacity: isPending ? 0.5 : 1 }}
>
首页
</button>
<button
onClick={() => handleTabChange('about')}
style={{ opacity: isPending ? 0.5 : 1 }}
>
关于
</button>
<button
onClick={() => handleTabChange('contact')}
style={{ opacity: isPending ? 0.5 : 1 }}
>
联系
</button>
</div>
<div>
{tab === 'home' && <HomeTab />}
{tab === 'about' && <AboutTab />}
{tab === 'contact' && <ContactTab />}
</div>
</div>
)
}
7. 迁移指南
从 React 17 迁移到 React 18
// 1. 更新 Root API
// 旧代码
import ReactDOM from 'react-dom'
ReactDOM.render(<App />, document.getElementById('root'))
// 新代码
import { createRoot } from 'react-dom/client'
const root = createRoot(document.getElementById('root'))
root.render(<App />)
// 2. 处理自动批处理
// 如果某些更新需要立即执行,使用 flushSync
import { flushSync } from 'react-dom'
function handleClick() {
flushSync(() => {
setCount((c) => c + 1)
})
// count 已经更新
}
// 3. 使用新的 Hooks
// 替换自定义 ID 生成逻辑
const id = useId() // 替代 Math.random() 或自定义 ID 生成
// 4. 更新第三方库
// 确保使用的第三方库支持 React 18
总结
React 18 的主要更新包括:
- 自动批处理:所有更新都会被自动批处理,提高性能
- 并发特性:startTransition、useDeferredValue 等新 API 支持并发渲染
- 新的 Root API:createRoot 和 hydrateRoot 替代旧的 API
- 新的 Hooks:useId、useSyncExternalStore、useInsertionEffect
- 严格模式改进:帮助发现副作用问题
- Suspense 改进:支持更多使用场景
这些更新让 React 应用具有更好的性能、用户体验和开发体验,特别是在处理大量数据和复杂交互时。