news 2026/9/10 3:18:31

Refine 中的 React 18 升级指南:新特性、API 迁移与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Refine 中的 React 18 升级指南:新特性、API 迁移与工程实践

Refine 中的 React 18 升级指南:新特性、API 迁移与工程实践

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

React 18 是 React 发展史上的一个分水岭版本,它首次把"并发渲染(Concurrent Rendering)"这一全新概念带入主流 React 应用。本指南基于 Refine 开源仓库中的技术文档,系统梳理 React 17 存在的问题、React 18 的核心变更(createRoothydrateRoot、自动批处理、Transitions 等),并结合仓库内真实示例源码,帮助你安全地把项目升级到 React 18 并重构既有代码。读完本文,你将掌握 React 18 升级的完整路径、新旧 API 的对应关系,以及在以 Refine 为代表的 React 应用中落地的具体写法。

React 18 是什么

在讨论"React 18 有哪些新东西"之前,先明确概念:任何从 18.0.0 起、低于 19.0.0 的稳定版 React 库,都被称为 React 18。

React 18 的诞生把并发渲染引入了 React 应用。此前 React 一直负责 DOM 渲染,并为开发者提供控制与追踪组件生命周期的工具;借助一批新能力,React 18 可以主动调整渲染过程来适配客户端设备——这正是并发模式下 React 应用性能优化的根基。

升级到 React 18

React 社区提供了多种安装方式,你可以新建一个 React 项目,也可以在现有项目中重装 React,从 React 17 迁移到 React 18。

方式一:CDN 引入(适合无需构建的页面)

在 HTML 的<script>标签中,用 CDN URL 作为src直接加载 React 18:

<!-- Load React. --> <!-- Note: when deploying, replace "development.js" with "production.min.js". --> <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script> <!-- Load our React component. --> <script src="app.js"></script>

注意:开发阶段使用development.js便于看到完整的警告与错误信息,部署上线时务必替换为production.min.js

方式二:NPM / Yarn 安装(适合单页应用与打包项目)

在项目根目录的终端执行以下命令即可自动检测并安装(或升级到)最新的 React 与 React DOM:

# NPM npm install react react-dom # Yarn yarn add react react-dom

从仓库现状看,升级并非终点:本仓库根目录 package.json 中已经使用@types/react: ^19.1.0@types/react-dom: ^19.1.0,说明 React 19 的类型体系已被广泛采用,React 18 只是这条升级路径上的重要一站——掌握 18 的迁移模式,就为后续升级打好了基础。

React 17 的问题

React 社区发现 17 及更早版本存在若干亟待改进的问题——如果 React 17 表现完美,就不会有 React 18 及更高版本了。根据 React 18.0.0 的 changelog,以下问题在 React 18 中得到修复:

  • 组件返回undefined时渲染报错:当组件返回值为undefined时,应用会直接崩溃并展示错误信息,控制台也会出现对应的报错提示。
  • 对已卸载组件调用setState产生警告:尝试更新已卸载组件的状态时,React 会警告可能的内存泄漏。
  • Strict Mode 下的 console 日志抑制:社区反馈,Strict Mode 中 console 日志被抑制会让开发者困惑——理应出现两次的日志只显示一次。
  • 内存消耗:React 17 及更早版本存在内存泄漏问题,尤其在已卸载组件上更为明显。

React 18 改变了什么

React 18 的重心全面转向"应用并发"。这一理念具体化为:

  • 新特性:自动批处理(Automatic Batching)、Transition、Suspense;
  • 新 API:createRoothydrateRootrenderToPipeableStreamrenderToReadableStream
  • 新 Hooks:useIduseTransitionuseDeferredValueuseSyncExternalStoreuseInsertionEffect
  • 行为变更:Strict Mode 更新、废弃ReactDOM.renderrenderToString

下面逐一深入这些变更。

客户端渲染(Client Rendering):从ReactDOM.rendercreateRoot

升级后若仍沿用 React 17 的ReactDOM.render()API,控制台会持续出现弃用警告。React 17 的典型写法是:导入组件后渲染进一个id="app"的 div 中:

import ReactDOM from "react-dom"; import App from "App"; const app = document.getElementById("app"); ReactDOM.render(<App />, app);

React 18 中改用从"react-dom/client"导入的createRoot()API:

import { createRoot } from "react-dom/client"; import App from "App"; const app = document.getElementById("app"); // create a root const root = createRoot(app); //render app to root root.render(<App />);

这一迁移模式在 Refine 仓库的示例中被广泛采用。以 examples/base-antd/src/index.tsx 为例,整个 Refine 应用的入口正是标准的createRoot写法:

import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; const container = document.getElementById("root"); // eslint-disable-next-line const root = createRoot(container!); root.render( <React.StrictMode> <App /> </React.StrictMode>, );

可以推断,仓库中 examples 目录下的绝大多数示例入口(如auth-antdbase-chakra-uitemplate-material-uipixels等)都统一采用了createRoot+ StrictMode 的启动模式,这本身就是 React 18 迁移后的"标准答案":先createRoot(container)创建根,再通过root.render()挂载组件树,StrictMode 在开发期帮助提前暴露不纯渲染等隐患。

水合(Hydration):从ReactDOM.hydratehydrateRoot

服务端渲染场景下,React 17 使用ReactDOM.hydrate()API 进行带水合的渲染:

import * as ReactDOM from "react-dom"; import App from "App"; const app = document.getElementById("app"); // Render with hydration. ReactDOM.hydrate(<App tab="home" />, app);

React 18 中,水合改用从"react-dom/client"导入的hydrateRoot()API,并且不再需要单独的render()方法:

import { hydrateRoot } from "react-dom/client"; import App from "App"; const app = document.getElementById("app"); const root = hydrateRoot(app, <App tab="home" />);

如果你同时关注流式渲染,React 18 还提供了renderToPipeableStream(Node 环境)与renderToReadableStream(Web/Edge 环境)两个新的服务端渲染 API,用于取代renderToString

渲染回调(Render Callback)的移除与替代

在 React 17 的render方法中,可以把回调函数作为第三个参数传入,使其在组件渲染或更新后执行:

import * as ReactDOM from 'react-dom'; import App from 'App'; const app = document.getElementById('app'); ReactDOM.render(app, <App tab="home" />, function() { // Called after initial render or any update. console.log('Rendered or Updated'). });

React 18 的createRoot不再支持回调参数,因为回调会影响应用在渐进式/部分水合下的运行时行为。官方推荐的替代方案是在根元素上使用 ref 回调、setTimeoutrequestIdleCallback

import { createRoot } from "react-dom/client"; function App({ callback }) { // Callback will be called when the div is first created. return ( <div ref={callback}> <h1>Hello World</h1> </div> ); } const app = document.getElementById("root"); const root = createRoot(app); root.render(<App callback={() => console.log("Rendered or Updated")} />);

这里 ref 回调在 div 首次创建时触发,既保证了"渲染完成后执行",又不破坏并发渲染的调度语义。

自动批处理(Automatic Batching)

在 React 17 及更早版本中,只有 React 事件处理器内的状态更新会被批处理。以handleClick为例,所有状态在事件回调函数结束时一次性变更,React 只重渲染一次:

const handleClick = () => { setFirstState("1"); setSecondState("2"); }

但状态更新一旦发生在 Promise、原生事件或 React 事件处理器之外,情况就不同了——每次setState都会触发一次重渲染:

fetch('https://api.com').then(() => { setFirstState("1"); setSecondState("2"); }) //OR setTimeout(() => { setFirstState("1"); setSecondState("2"); })

React 18 的createRoot()使无论状态更新发生在应用何处,都会被自动批处理,React 在所有状态更新完成后统一重渲染页面。由于这是一项破坏性变更,如果你确实需要强制同步刷新,可以使用flushSync()API 关闭某段代码的自动批处理:

import { flushSync } from "react-dom"; function handleClick() { flushSync(() => { setFirstState("1"); }); flushSync(() => { setSecondState("2"); }); }

上面的代码中,每次调用flushSync()都会立即更新状态并让 React 重渲染一次——代价是失去批处理带来的性能收益,因此应谨慎、局部地使用。

过渡(Transitions):区分紧急与非紧急更新

Transitions 让你把"需要紧急/立即响应的状态更新"和"可以延后的状态更新"区分开来。搜索框是经典场景:用户输入时你要立即给出视觉反馈,但真正触发搜索可以等用户输完再开始:

import { startTransition } from "react"; // Urgent: Show what was currently typed setSearchCurrentValue(input); startTransition(() => { // Not-urgent: Show what was finally typed setSearchFinalValue(input); });

这里用startTransition()代替setTimeout()来监控状态更新:setSearchCurrentValue()只更新与即时反馈相关的状态,setSearchFinalValue()更新最终用于触发搜索的状态。与setTimeout相比,startTransition的更新可以被中断可以追踪 pending 状态,并且立即执行——这正是并发渲染调度能力的体现。与之配套的还有useTransitionHook(可获取 pending 标志)与useDeferredValueHook(延迟派生值)。

放弃对 Internet Explorer 的支持

React 社区同时放弃了 Internet Explorer。这意味着只有 React 17 及之前支持的浏览器特性能在 IE 上运行;PromiseObject.assignSymbol等现代浏览器特性将不再为 IE 提供 polyfill。

React 18 相对 React 17 的优势

了解了上述差异后,你或许还在犹豫是否要切换到 React 18。一个没有带来更多价值的新版本是不值得被采纳的。

  • 并发是首要优势:并发渲染不是某个单一功能,而是一个全新概念,它让运行在 React 18 及更高版本上的应用能够针对客户端设备优化性能——配合useTransitionuseDeferredValue、Suspense 等 API,可以精细控制渲染优先级,避免大计算量更新阻塞用户交互。
  • 更优的内存管理:React 18 通过在卸载时清理后台任务,降低内存泄漏风险,直接回应了 React 17 时代"未卸载组件内存泄漏"的痛点。

在 Refine 项目中的落地实践

把视角放回本仓库,React 18 的迁移并不只是"换个入口文件"这么简单,它还体现在项目工程化的方方面面:

  1. 入口统一为createRoot:以 examples/base-antd/src/index.tsx 为代表的示例入口,均采用createRoot(container!)+root.render(<React.StrictMode><App /></React.StrictMode>)的写法,这是 React 18 客户端渲染的推荐形态,升级时可直接对照改写。
  2. 类型与依赖同步升级:根目录 package.json 中的@types/react@types/react-dom已指向^19.1.0,说明类型定义需要与运行时版本匹配推进;从 React 17 迁移到 18 时,也应同步把@types/react升级到 18.x 或更高。
  3. 依赖版本由 pnpm 工作区统一管理:本仓库通过 pnpm-workspace.yaml 与 lerna.json 组织多包结构,升级 React 这类核心依赖时,应先确认各子包(@refinedev/core@refinedev/antd@refinedev/mui等)与目标 React 版本的兼容性,再统一执行安装命令。

结论

读完本篇指南,你应该已经清楚:React 18 是一次从"渲染机制"到"开发心智"的全面升级。从ReactDOM.render迁移到createRoot、从ReactDOM.hydrate迁移到hydrateRoot,用 ref 回调替代渲染回调,理解自动批处理并用flushSync精准控制同步刷新,再用startTransition/useTransition编排紧急与非紧急更新——这套组合拳足以让你平滑完成 React 17 → 18 的迁移,并把并发能力真正用在自己的应用里。

最后,建议持续关注 React 库的 changelog,及时跟进新版本与变更说明,保持与 React 社区的同步——版本升级永远是一件"常做常新"的工程任务。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 3:17:24

STM32 RS485通信实战:从硬件电路到HAL库代码

简介&#xff1a;面向STM32F103平台的RS485通信参考工程&#xff0c;适合嵌入式开发入门者、工业自动化及远程监控项目技术人员&#xff0c;重点解决长距离多节点串行通信中的UART配置、485驱动器控制与收发切换问题。压缩包含122个文件&#xff0c;以C源码、H头文件和启动汇编…

作者头像 李华
网站建设 2026/9/10 3:17:19

TMS320VC5509A上McBSP与DMA协同驱动实战指南

简介&#xff1a;本资源是面向嵌入式DSP开发者的TMS320VC5509A芯片DMA实战工程&#xff0c;聚焦McBSP外设与DMA协同工作的底层驱动实现&#xff0c;适用于通信、音频实时处理等对数据吞吐与时序敏感的应用场景&#xff0c;适合具备C语言基础和TI C55x架构初步认知的中级开发者学…

作者头像 李华
网站建设 2026/9/10 3:15:50

Verification Report

Verification Report 【免费下载链接】oh-my-claudecode Teams-first Multi-agent orchestration for Claude Code 项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-claudecode Verdict Status: PASS | FAIL | INCOMPLETE Confidence: high | medium | low Bl…

作者头像 李华
网站建设 2026/9/10 3:13:55

补全项目信息,获得高质量技术博客的生成基础

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 3:13:06

读懂ESP32核心板原理图:供电、引脚占用与MicroPython实战排查

简介&#xff1a;面向ESP32入门者与物联网项目开发者&#xff0c;压缩包整合了核心板及扩展板硬件原理图、ESP32中文手册和MicroPython实验例程&#xff0c;涵盖电路设计、传感器接入与WiFi/蓝牙通信等场景&#xff0c;可帮助读者从硬件到代码形成完整认知。包体共104个文件、约…

作者头像 李华