跳到主要内容

React 18 新特性

问题

React 18 有哪些新特性?Concurrent Mode 是什么?useTransition 和 useDeferredValue 怎么用?

答案

React 18 的核心是 Concurrent Rendering(并发渲染),带来了自动批处理、Transitions、Suspense 增强等重要特性。

核心特性概览

特性说明
Concurrent Rendering可中断渲染,优先级调度
Automatic Batching所有场景自动批量更新
Transitions区分紧急和非紧急更新
Suspense 增强支持数据获取,SSR 流式渲染
新 HooksuseTransition、useDeferredValue、useId 等
新 Root APIcreateRoot 替代 render

新的 Root API

React 17

import ReactDOM from 'react-dom';

// 旧 API
ReactDOM.render(<App />, document.getElementById('root'));

// 卸载
ReactDOM.unmountComponentAtNode(document.getElementById('root'));

React 18

import { createRoot } from 'react-dom/client';

// 新 API
const root = createRoot(document.getElementById('root')!);
root.render(<App />);

// 卸载
root.unmount();
为什么改变?

新 API 是启用 Concurrent 特性的前提。createRoot 启用了并发渲染模式。

Automatic Batching

React 17 的限制

// React 17
function handleClick() {
// ✅ 批量更新
setCount(c => c + 1);
setFlag(true);
}

setTimeout(() => {
// ❌ 不批量,两次渲染
setCount(c => c + 1);
setFlag(true);
}, 0);

fetch('/api').then(() => {
// ❌ 不批量,两次渲染
setCount(c => c + 1);
setFlag(true);
});

React 18 自动批处理

// React 18 - 所有场景都批量更新
function handleClick() {
// ✅ 批量
setCount(c => c + 1);
setFlag(true);
}

setTimeout(() => {
// ✅ 批量(新!)
setCount(c => c + 1);
setFlag(true);
}, 0);

fetch('/api').then(() => {
// ✅ 批量(新!)
setCount(c => c + 1);
setFlag(true);
});

document.addEventListener('click', () => {
// ✅ 批量(新!)
setCount(c => c + 1);
setFlag(true);
});

退出批处理

import { flushSync } from 'react-dom';

function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// DOM 已更新

flushSync(() => {
setFlag(true);
});
// DOM 已更新
}

Concurrent Rendering

并发渲染原理

优先级调度

// React 18 Lanes 优先级
const SyncLane = 0b0000001; // 同步(最高)
const InputContinuousLane = 0b0000100; // 连续输入
const DefaultLane = 0b0010000; // 默认
const TransitionLane = 0b1000000; // 过渡(低)

useTransition

标记非紧急更新,保持 UI 响应:

import { useTransition, useState } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<string[]>([]);
const [isPending, startTransition] = useTransition();

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;

// 紧急更新:输入框立即响应
setQuery(value);

// 非紧急更新:可以被中断
startTransition(() => {
// 昂贵的计算
const filtered = filterLargeList(value);
setResults(filtered);
});
};

return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ul>
{results.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}

useTransition vs setTimeout

特性useTransitionsetTimeout
阻塞输入❌ 不阻塞✅ 不阻塞
立即开始✅ 是❌ 延迟
可中断✅ 是❌ 否
显示中间状态✅ isPending需手动管理
// setTimeout:延迟执行
const handleChange = (value: string) => {
setQuery(value);
setTimeout(() => {
setResults(filterLargeList(value));
}, 0);
};

// useTransition:立即开始,可中断
const handleChange = (value: string) => {
setQuery(value);
startTransition(() => {
setResults(filterLargeList(value));
});
};

useDeferredValue

延迟更新某个值,类似防抖但更智能:

import { useDeferredValue, useMemo, useState } from 'react';

function SearchResults({ query }: { query: string }) {
// 延迟 query 更新
const deferredQuery = useDeferredValue(query);

// 只有 deferredQuery 变化时才重新计算
const results = useMemo(
() => filterLargeList(deferredQuery),
[deferredQuery]
);

// 判断是否在等待
const isStale = query !== deferredQuery;

return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{results.map(item => (
<div key={item}>{item}</div>
))}
</div>
);
}

function SearchPage() {
const [query, setQuery] = useState('');

return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
<SearchResults query={query} />
</div>
);
}

useTransition vs useDeferredValue

特性useTransitionuseDeferredValue
控制对象状态更新函数
适用场景控制自己的 setState控制传入的 props
返回值[isPending, startTransition]deferredValue
// useTransition:控制如何更新状态
const [isPending, startTransition] = useTransition();
startTransition(() => {
setData(newData);
});

// useDeferredValue:控制使用哪个值
const deferredData = useDeferredValue(data);
// 输入快速变化时,deferredData 更新会延迟

Suspense 增强

数据获取支持

// 配合支持 Suspense 的数据获取库
import { Suspense } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';

function UserProfile({ id }: { id: string }) {
// 数据加载时会抛出 Promise
const { data: user } = useSuspenseQuery({
queryKey: ['user', id],
queryFn: () => fetchUser(id),
});

return <div>{user.name}</div>;
}

function App() {
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile id="123" />
</Suspense>
);
}

Suspense List(实验性)

import { SuspenseList, Suspense } from 'react';

function App() {
return (
<SuspenseList revealOrder="forwards" tail="collapsed">
<Suspense fallback={<Skeleton />}>
<ProfileDetails />
</Suspense>
<Suspense fallback={<Skeleton />}>
<ProfileTimeline />
</Suspense>
<Suspense fallback={<Skeleton />}>
<ProfileFriends />
</Suspense>
</SuspenseList>
);
}
属性说明
revealOrderforwards从上到下依次显示
revealOrderbackwards从下到上依次显示
revealOrdertogether全部就绪后一起显示
tailcollapsed只显示一个 fallback
tailhidden不显示 fallback

新 Hooks

useId

生成稳定唯一的 ID,支持 SSR:

import { useId } from 'react';

function FormField({ label }: { label: string }) {
const id = useId();

return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} />
</div>
);
}

// 生成类似 :r1:, :r2: 的 ID
// 服务端和客户端一致

useSyncExternalStore

订阅外部存储,支持 Concurrent 模式:

import { useSyncExternalStore } from 'react';

function useOnlineStatus() {
const isOnline = useSyncExternalStore(
// subscribe
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
// getSnapshot
() => navigator.onLine,
// getServerSnapshot (SSR)
() => true
);

return isOnline;
}

function StatusBar() {
const isOnline = useOnlineStatus();
return <div>{isOnline ? '🟢 Online' : '🔴 Offline'}</div>;
}

useInsertionEffect

在 DOM 变更前同步执行,用于 CSS-in-JS 库:

import { useInsertionEffect } from 'react';

// 用于 CSS-in-JS 库
function useCSS(rule: string) {
useInsertionEffect(() => {
// 在 DOM 变更前注入样式
const style = document.createElement('style');
style.innerHTML = rule;
document.head.appendChild(style);

return () => {
document.head.removeChild(style);
};
}, [rule]);
}

Server Components

RSC 概念

// app/page.tsx - Server Component(默认)
async function Page() {
// 可以直接访问数据库、文件系统
const data = await db.query('SELECT * FROM posts');

return (
<div>
{data.map(post => (
<PostCard key={post.id} post={post} />
))}
<LikeButton /> {/* Client Component */}
</div>
);
}

// components/LikeButton.tsx - Client Component
'use client';

import { useState } from 'react';

export function LikeButton() {
const [liked, setLiked] = useState(false);

return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
);
}
特性Server ComponentClient Component
运行环境服务端客户端
可用 Hooks所有
事件处理
访问后端直接访问需要 API
Bundle不打包打包到客户端

Streaming SSR

传统 SSR

Streaming SSR

// 使用 Suspense 实现 Streaming
function Page() {
return (
<div>
<Header />
<Suspense fallback={<ArticleSkeleton />}>
<Article /> {/* 数据就绪后流式发送 */}
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<Comments /> {/* 数据就绪后流式发送 */}
</Suspense>
</div>
);
}

常见面试问题

Q1: React 18 的 Concurrent Mode 是什么?

答案

Concurrent Mode(并发模式)是 React 18 的核心特性,让 React 可以同时准备多个版本的 UI

核心能力

  1. 可中断渲染:高优先级更新可中断低优先级渲染
  2. 优先级调度:紧急更新(输入)优先于非紧急更新(列表)
  3. 时间切片:将长任务拆分,保持页面响应
// 启用并发模式
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);

// 使用并发特性
const [isPending, startTransition] = useTransition();
startTransition(() => {
// 这个更新可被中断
setLargeList(filtered);
});

Q2: useTransition 和 useDeferredValue 有什么区别?

答案

特性useTransitionuseDeferredValue
控制对象setState 函数
返回值[isPending, startTransition]deferredValue
使用场景控制自己的状态更新延迟使用传入的 props
// useTransition:你控制状态
const [isPending, startTransition] = useTransition();
const handleChange = (value) => {
setInput(value);
startTransition(() => {
setFilteredList(filter(value)); // 低优先级
});
};

// useDeferredValue:你只接收值
function List({ searchQuery }) {
const deferredQuery = useDeferredValue(searchQuery);
// searchQuery 快速变化时,deferredQuery 延迟更新
}

Q3: React 18 的自动批处理有什么改进?

答案

React 17 只在 React 事件处理器中批处理。React 18 所有场景都自动批处理

// React 18 全场景批处理
setTimeout(() => {
setCount(1);
setFlag(true);
// 一次渲染(React 17 是两次)
}, 0);

fetch('/api').then(() => {
setCount(1);
setFlag(true);
// 一次渲染
});

退出批处理:使用 flushSync

Q4: Suspense 在 React 18 有什么增强?

答案

版本Suspense 能力
React 16只支持 React.lazy
React 18数据获取、SSR Streaming
// 数据获取
<Suspense fallback={<Loading />}>
<UserData /> {/* 使用支持 Suspense 的数据获取库 */}
</Suspense>

// SSR Streaming
// Suspense 边界内的内容可以流式发送

Q5: 什么是 Server Components?有什么好处?

答案

Server Components 是只在服务端运行的 React 组件。

好处

  1. 零客户端包体积:代码不发送到客户端
  2. 直接访问后端:直接查数据库、读文件
  3. 自动代码分割:Client Components 按需加载
// Server Component(默认)
async function Page() {
const data = await db.query('...'); // 直接访问数据库
return <div>{data.title}</div>;
}

// Client Component
'use client';
function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>Like</button>;
}

限制:Server Components 不能使用 Hooks 和事件处理。

相关链接