React 18 核心新特性解析
一、自动批处理(Auto Batching)⭐⭐⭐⭐⭐
批处理是指 React 将多个状态更新,聚合到一次 render 中执行,以提升性能。但在 React 18 之前,React 只会在事件回调中使用批处理,而在 Promise、setTimeout、原生事件等场景下,是不能使用批处理的。而在 React 18 中,所有的状态更新,都会自动使用批处理,不关心场景。
ruby
// React 17(仅事件处理器内批处理) const handleClick = () => { setCount(c => c + 1); // 触发渲染 setFlag(f => !f); // 触发渲染 } // 最终只1次渲染 // 非事件场景(如Promise、setTimeout) fetchData().then(() => { setCount(c => c + 1); // 立即渲染 setFlag(f => !f); // 再次渲染 }); // React 18(全场景自动批处理) fetchData().then(() => { setCount(c => c + 1); setFlag(f => !f); }); // 合并为单次渲染 setTimeout(() => { setCount(c => c + 1); setFlag(f => !f); // React 只会 re-render 一次,这就是批处理 }, 1000);
强制立即渲染(应急方案): 如果你在某种场景下不想使用批处理,你可以通过 flushSync来强制同步执行(比如:你需要在状态更新后,立刻读取新 DOM 上的数据等。)
javascript
import { flushSync } from 'react-dom'; function handleClick() { flushSync(() => { setCounter(c => c + 1); }); // React 更新一次 DOM flushSync(() => { setFlag(f => !f); }); // React 更新一次 DOM }
二、并发模式(Concurrent Mode)⭐⭐⭐⭐⭐
CM 本身并不是一个功能,而是一个底层设计,它能 保持界面响应性同时执行耗时操作 。
在以前,React 在状态变更后,会开始准备虚拟 DOM,然后渲染真实 DOM,整个流程是串行的。一旦开始触发更新,只能等流程完全结束,期间是无法中断的。

在 CM 模式下,React 在执行过程中,每执行一个 Fiber,都会看看有没有更高优先级的更新,如果有,则当前低优先级的的更新会被暂停,待高优先级任务执行完之后,再继续执行或重新执行。

不过对于普通开发者来说,我们一般是不会感知到 CM 的存在的,在升级到 React 18 之后,我们的项目不会有任何变化。
我们需要关注的是基于 CM 实现的上层功能,比如 Suspense、Transitions、streaming server rendering(流式服务端渲染), 等等。
核心API:startTransition
React 的状态更新可以分为两类:
- 紧急更新(Urgent updates):比如打字、点击、拖动等,需要立即响应的行为,如果不立即响应会给人很卡,或者出问题了的感觉
- 过渡更新(Transition updates):将 UI 从一个视图过渡到另一个视图。不需要即时响应,有些延迟是可以接受的。
- 举个例子:比如你在家看电影,这时候快递送达在敲你的门,startTransition 出来之前你会先看完整部电影再去拿快递,startTransition 之后识别了拿快递为高优先级,电影为低优先级,这时候你会暂定电影的播放,开门拿快递,然后回来继续看电影。
React 并不能自动识别哪些更新是优先级更高的,CM 只是提供了可中断的能力,也就是说默认情况下,所有的更新都是紧急更新。
例如键盘输入事件:
matlab
import { useState, startTransition } from 'react'; function SearchBox() { const [keywords, setKeywords] = useState(''); const [searchResults, setSearchResults] = useState([]); const handleChange = (e) => { setKeywords(e.target.value); // 即时更新输入框 startTransition(() => { // 延迟处理搜索 performHeavySearch(e.target.value).then(results => { setSearchResults(results); }); }); }; return ( <> <input value={keywords} onChange={handleChange} /> <SearchResults results={searchResults} /> </> ); }
毕达哥拉斯树
下方 Gif 图中可以看出,当我们把树的节点拉满,然后操作倾斜树,此时页面的真实 dom 会跟随滑动条的数值每一次的更新实时渲染,滑动条跟树都在抢渲染线程,你会感觉到滑动条拖动非常卡顿,体验感差。
开启 startTransition 后,滑动条的数值更新被标记为 高优先级 DOM,被实时渲染,树的渲染为低优先级 DOM,你会发现滑动条的拖动变得流畅,不再卡顿,而树渲染的卡顿被认为是可以接受的。
三、新 Hooks ⭐⭐⭐⭐
useId:跨平台唯一ID生成
支持同一个组件在客户端和服务端生成相同的唯一的 ID,避免 hydration 的不兼容。原理是每个 id 代表该组件在组件树中的层级结构。
javascript
function Checkbox() { const id = useId(); return ( <> <label htmlFor={id}>Accept</label> <input id={id} type="checkbox" /> </> ); }
useDeferredValue:延迟更新非关键内容
useDeferredValue 可以让一个 state 延迟生效,只有当前没有紧急更新时,该值才会变为最新值。useDeferredValue 和 startTransition 一样,都是标记了一次非紧急更新。
上边 startTransition 的例子,就可以用 useDeferredValue来实现。
javascript
const [treeLeanInput, setTreeLeanInput] = useState(0); const deferredValue = useDeferredValue(treeLeanInput); function changeTreeLean(event) { const value = Number(event.target.value); setTreeLeanInput(value) } return ( <> <input type="range" value={treeLeanInput} onChange={changeTreeLean} /> <Pythagoras lean={deferredValue} /> </> )
四、根节点API升级 ⭐⭐⭐
python
// React 17 import ReactDOM from 'react-dom'; ReactDOM.render(<App />, document.getElementById('root')); // React 18 import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root')); root.render(<App />);
React 19 核心新特性解析
一、服务器组件 Server Component - 主要更新
Server Components 是 React 19 中最大的变化之一,它提供了一种在服务器端渲染组件的新方法,并提供了更快、更高效的用户体验。
- 缩短初始页面加载时间: 通过在服务器端渲染组件,React 19 减少了发送到客户端的 JavaScript 数量(只需返回渲染后的结果),从而加快了初始加载时间。在将页面发送到客户端之前,还可以在服务器上从数据库中对数据进行提取。
- 增强的代码可移植性: 服务器组件允许开发人员编同时可以在服务器和客户端上运行的组件,从而减少重复,提高可维护性,并更轻松地在代码库中共享逻辑。
- 更好的 SEO: 组件的服务器端渲染可确保发送到客户端的 HTML 已填充内容,从而使搜索引擎更容易抓取和索引网站。
xml
// Users.server.jsx // Server Component: Fetches data and returns JSX export default async function Users() { const res = await fetch("https://api.example.com/users"); const users = await res.json(); return ( <div> <h1>Users</h1> {users.map((user) => ( <div key={user.id}> <h2>{user.name}</h2> <p>{user.role}</p> </div> ))} </div> ); }
React 通过服务器端渲染 (SSR) 进行了改进,它将第一个渲染移动到服务器。提供给用户的 HTML 不再为空,它提高了用户看到初始 UI 的速度。但是,仍需要获取数据才能显示实际内容。
我们第一次可以在 UI 呈现并显示给用户之前获取数据,提供给用户的 HTML 在第一次渲染时完全填充了实际内容,无需获取其他数据或第二次渲染。
二、Actions(异步操作的革命性改进)⭐⭐⭐⭐⭐
异步数据更新一直是 React 应用中的难点之一。React 19 引入了 Actions,通过支持异步函数来管理数据变更、加载状态、错误处理和乐观更新(optimistic updates),使复杂逻辑的处理变得更加简单。
自动管理 Pending 状态: 使用 useActionState 和 useFormStatus 等新钩子轻松处理表单的加载状态。
内置乐观更新支持: 通过 useOptimistic 实现实时数据更新。
更智能的错误处理: 集成错误边界,简化错误回退逻辑。
useActionState 管理异步函数状态
useActionState 是 React 19 新增的一个 Hook,用来管理异步函数,自动维护了 data、action、pending 等状态。
经过 useActionState 改造的代码如下:
bash
// React 18 - 手动管理状态 function Form() { const [isPending, setIsPending] = useState(false); async function handleSubmit() { setIsPending(true); await fetch('/api'); setIsPending(false); } return ( <form onSubmit={handleSubmit}> <button disabled={isPending}> {isPending ? 'Submitting...' : 'Submit'} </button> </form> ); } // React 19 - 自动化状态管理 function Form() { // 接受一个异步请求函数,返回 [data、action、pending] const [error, handleSubmit, isPending] = useActionState( async () => { // 自动处理pending状态 await fetch('/api'); } ); return ( <form action={handleSubmit}> <button disabled={isPending}> {isPending ? 'Submitting...' : 'Submit'} </button> </form> ); }
useOptimistic 乐观更新
乐观更新是一种常见的体验优化手段,在发送异步请求之前,我们默认请求是成功的,让用户立即看到成功后的状态。
官方示例: 提交表单更新 name,可以立即将新的 name 更新到 UI 中。请求成功则 UI 不变,请求失败则 UI 回滚。
vbnet
function ChangeName() { const [name, setName] = useState(""); // 定义乐观更新的状态 const [optimisticName, setOptimisticName] = useOptimistic(name); const submitAction = async (formData) => { const newName = formData.get("name"); // 请求之前,先把状态更新到 optimisticLike setOptimisticName(newName); try { await updateName(newName); // 成功之后,更新最终状态 setName(newName); } catch (e) { console.error(e); } }; return ( <form action={submitAction}> <p>Your name is: {optimisticName}</p> <p> <label>Change Name:</label> <input type="text" name="name" disabled={name !== optimisticName} /> </p> </form> ); }
useFormStatus 获取表单状态
useFormStatus 是 React 19 新增的一个 Hook,主要用来快捷读取到最近的父级 form 表单的数据,其实就是类似 Context 的封装。
javascript
import { useFormStatus } from "react-dom"; import action from './actions'; function Submit() { const status = useFormStatus(); return <button disabled={status.pending}>Submit</button> } export default function App() { return ( <form action={action}> <Submit /> </form> ); } const { pending, data, method, action } = useFormStatus(); useFormStatus 能拿到父级最近的 form 的状态: pending:是否正在提交中 data:表单正在提交的数据,如果 form 没有被提交,则为 null method:form 的 method 属性,get 或 post action:form 的 action 属性,如果 action 不是函数,则为 null useFormStatus 使用场景较窄,绝大部分开发者不会用到。
三、use Hook(异步处理新范式)⭐⭐⭐⭐⭐
use 是 React 19 新增的一个特性,支持处理 Promise 和 Context。
假如我们要实现这样一个需求:请求接口数据,请求过程中,显示 loading,请求成功,展示数据。
use 的使用有一些注意事项:
- 需要在组件或 Hook 内部使用
- use 可以在条件语句(比如 if)或者循环(比如 for)里面调用
以前我们可能会这样写代码:
matlab
// React 18 - useEffect方案 function ReactUseDemo() { const [data, setData] = useState(""); const [loading, setLoading] = useState(false); useEffect(() => { setLoading(true); getList() .then((res) => { setData(res); setLoading(false); }) .catch(() => { setLoading(false); }); }, []); if (loading) return <div>Loading...</div>; return <div>{data}</div>; } // React 19 - use Hook方案 export default function ReactUseDemo() { return ( <Suspense fallback={<div>Loading...</div>}> <ChildCompont /> </Suspense> ); } function ChildCompont() { const data = use(getData()); return <div>{data}</div>; }
四、Ref作为Prop(API统一)⭐⭐⭐⭐
在之前,父组件传递 ref 给子组件,子组件如果要消费,则必须通过 forwardRef 来消费。
sql
// React 18 之前 function RefDemo() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); }; return ( <div> <Input ref={inputRef} /> <button onClick={focusInput}>Focus</button> </div> ); } const Input = forwardRef((props, ref) => { return <input ref={ref} />; }); // React 19 export const Input = ({ ref }) => { return <input ref={ref} />; };
五、预加载资源 Preloading resources ⭐⭐⭐
React 19 添加了几个新的 API,通过加载和预加载脚本、样式表和字体等资源来提高页面加载性能和用户体验。
这段 React 代码将生成以下 HTML 输出。请注意,链接和脚本的优先级和排序是按它们应该加载的时间,而不是根据它们在 React 中的使用顺序。
lua
// React code import { prefetchDNS, preconnect, preload, preinit } from "react-dom"; function MyComponent() { preinit("https://.../path/to/some/script.js", { as: "script" }); preload("https://.../path/to/some/font.woff", { as: "font" }); preload("https://.../path/to/some/stylesheet.css", { as: "style" }); prefetchDNS("https://..."); preconnect("https://..."); }
六、文档元数据支持 Document Metadata ⭐⭐
React 19 将原生提升和渲染 title、link 和 meta 标签,甚至可以从嵌套组件中。不再需要第三方解决方案来管理这些标签。
xml
// React 18 - 第三方库方案 import { Helmet } from 'react-helmet'; function ProductPage() { return ( <> <Helmet> <title>商品详情页</title> <meta name="description" content="商品详细信息页面" /> </Helmet> {/* ... */} </> ); } // React 19 - 原生支持 function ProductPage() { return ( <> <title>商品详情页</title> <meta name="description" content="商品详细信息页面" /> {/* ... */} </> ); }
完结撒花🎉
作者:陶帅星(汽车之家前端工程师)