浅谈React19的破坏性更新

2024年12月,React 19 正式发布,至今已过去大半年。尽管目前多数项目仍在使用 React 18,但我们可以通过官方文档和 GitHub 了解其带来的关键新特性和破坏性变更。以下是我总结的一些重要更新。

React相关文档也可参考这个地址:传送门

1、Optimistic (直译为:乐观)

React19新加入了一个概念,叫做乐观 。对应的API为useOptimistic。乐观更新是一种 UI 设计模式,其核心思想是:

"先相信操作会成功,提前更新 UI;如果失败了,再回滚到之前的状态。 这与传统的"悲观更新"(先等待服务器响应,成功后再更新 UI)形成对比。

为什么需要乐观更新?
  • 提升用户体验:用户点击按钮后,UI 立即响应,无需等待网络延迟。
  • 感觉更快:即使网络慢,用户也能看到操作"已生效",减少等待焦虑。
  • 现代应用的标准做法:如社交媒体点赞、评论删除等,都采用此模式。

示例

js 复制代码
import { useOptimistic, useState } from "react";
import { postComment as apiPostComment } from "../utils";

const apiPostComment = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('comment success')
    }, 1000)
  })
}

// 评论项组件
function CommentItem({ comment }) {
  return (
    <div className="border p-3 mb-2 rounded bg-white shadow-sm">
      <p className="text-gray-800">{comment.text}</p>
      {comment.isPending && (
        <small className="text-blue-500 mt-1 block">正在提交...</small>
      )}
    </div>
  );
}

// 评论区域组件
function CommentSection({ commentList, onAddComment }) {
  const [pendingCommentId, setPendingCommentId] = useState(0);

  // 乐观更新:立即显示待定评论
  const [displayedComments, addPendingComment] = useOptimistic(
    commentList,
    (state, newCommentText) => {
      const id = `pending-${pendingCommentId}`;
      return [
        ...state,
        {
          id,
          text: newCommentText,
          isPending: true,
        },
      ];
    }
  );

  // 表单提交处理器
  async function handleAddComment(formData) {
    const content = formData.get("commentContent");
    if (!content?.trim()) return;

    // 生成本次提交的临时 ID
    const currentPendingId = pendingCommentId;
    setPendingCommentId((prev) => prev + 1);

    // 1. 立即乐观更新 UI
    addPendingComment(content);

    try {
      // 2. 发起真实请求
      const result = await onAddComment(formData, `pending-${currentPendingId}`);

      if (result.error) {
        throw new Error(result.error.message);
      }
    } catch (error) {
      console.error("评论提交失败:", error.message);
      // 注意:useOptimistic 不会自动回滚,需配合其他状态管理
    }
  }

  return (
    <div className="comment-container">
      {/* 渲染当前显示的评论(含乐观更新) */}
      {displayedComments.length === 0 ? (
        <p className="text-gray-500 italic">暂无评论</p>
      ) : (
        displayedComments.map((comment) => (
          <CommentItem key={comment.id} comment={comment} />
        ))
      )}

      {/* 添加评论表单 */}
      <form onSubmit={handleAddComment} className="mt-4 space-y-3">
        <textarea
          name="commentContent"
          placeholder="写下你的评论..."
          rows="3"
          className="w-full border rounded p-2 focus:outline-none focus:ring-2 focus:ring-blue-300"
          autoComplete="off"
        />
        <button
          type="submit"
          className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded transition"
        >
          发布评论
        </button>
      </form>
    </div>
  );
}

// 页面组件:评论功能演示
export default function OptimisticCommentDemo() {
  const [errorMessage, setErrorMessage] = useState("");
  const [comments, setComments] = useState([
    { id: "initial-1", text: "第一条评论!", isPending: false },
  ]);

  // 处理真实评论提交
  async function handleCommentSubmit(formData, pendingId) {
    const content = formData.get("commentContent");

    // 模拟 API 调用
    const response = await apiPostComment({ content });

    if (response.error) {
      setErrorMessage(`"${content}" 提交失败:${response.error.message}`);
      return response;
    } else {
      setErrorMessage("");
      // ✅ 成功后:添加真实评论(不带 isPending)
      setComments((prev) => [
        ...prev,
        {
          id: `comment-${Date.now()}`,
          text: response.data.text,
          isPending: false,
        },
      ]);
    }

    return response;
  }

  return (
    <div className="p-6 max-w-2xl mx-auto">
      <h2 className="text-2xl font-bold text-gray-800 mb-4">
        乐观更新评论系统演示
      </h2>

      <CommentSection commentList={comments} onAddComment={handleCommentSubmit} />

      {errorMessage && (
        <p className="text-red-600 mt-4 text-sm bg-red-50 p-3 rounded">
          {errorMessage}
        </p>
      )}
    </div>
  );
}

2、use (支持异步)

1、use可读取promise

示例:

js 复制代码
import { useState, use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

// 模拟异步获取天气数据
function fetchWeatherData(city) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 50% 概率失败,增加错误边界演示效果
      if (Math.random() < 0.5) {
        reject(new Error("天气服务暂时不可用"));
      } else {
        resolve({
          city,
          temperature: Math.round(Math.random() * 30),
          condition: ["晴", "多云", "小雨", "雷阵雨"][Math.floor(Math.random() * 4)],
          lastUpdated: new Date().toLocaleTimeString(),
        });
      }
    }, 1200);
  });
}

export default function WeatherDashboard() {
  const [weatherQuery, setWeatherQuery] = useState(null);
  const [isFetching, setFetching] = useState(false);

  const handleFetchWeather = () => {
    setWeatherQuery(fetchWeatherData("杭州"));
    setFetching(true);
  };

  if (!isFetching) {
    return (
      <div className="text-center">
        <h2 className="text-xl font-semibold mb-4">🌤️ 天气信息看板</h2>
        <button
          onClick={handleFetchWeather}
          className="bg-green-500 hover:bg-green-600 text-white px-5 py-2 rounded transition"
        >
          获取杭州天气
        </button>
      </div>
    );
  }

  return <WeatherDisplay weatherPromise={weatherQuery} />;
}

// 展示天气数据的容器(含错误和加载状态)
function WeatherDisplay({ weatherPromise }) {
  console.log(
    "%c [ WeatherDisplay ]-38",
    "font-size:13px; background:#4B5563; color:#fff; padding:2px 6px;",
    "渲染 WeatherDisplay"
  );

  return (
    <div className="max-w-md mx-auto p-4 border rounded-lg shadow bg-white">
      <h3 className="text-lg font-medium text-gray-800 mb-3">🌤️ 实时天气</h3>

      <ErrorBoundary fallback={<WeatherError />}>
        <Suspense fallback={<WeatherLoading />}>
          <WeatherCard dataPromise={weatherPromise} />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

// 加载中状态
function WeatherLoading() {
  return (
    <div className="text-center text-gray-500 animate-pulse">
      <p>📡 正在连接天气服务器...</p>
      <p className="text-sm mt-1">请稍候</p>
    </div>
  );
}

// 错误状态
function WeatherError() {
  return (
    <div className="text-center text-red-600">
      <p>❌ 获取天气失败</p>
      <p className="text-sm mt-1">请检查网络或稍后重试</p>
    </div>
  );
}

// 实际渲染天气数据(使用 use)
function WeatherCard({ dataPromise }) {
  const weather = use(dataPromise);

  return (
    <div>
      <p><strong>城市:</strong>{weather.city}</p>
      <p><strong>温度:</strong>{weather.temperature}°C</p>
      <p><strong>天气:</strong>{weather.condition}</p>
      <p className="text-xs text-gray-500 mt-2">
        更新时间:{weather.lastUpdated}
      </p>
    </div>
  );
}

2、use可读取context

示例:

js 复制代码
import {use} from 'react';
import ThemeContext from './ThemeContext'

function Heading({children}) {
  if (children == null) {
    return null;
  }
  
  const theme = use(ThemeContext);
  return (
    <h1 style={{color: theme.color}}>
      {children}
    </h1>
  );

3、Ref 支持在 props 中转发使用

从19开始,ref可以作为prop在函数组件中使用了。之前函数组件中想要使用ref,必须使用forwardRef。这表示着从React19开始,forwardRef可能要被弃用了。会对一些三方组件库有比较大的影响,因为基本上每个组件都会用到ref,之前的写法都是使用的forwardRef

1、React19给ref加上了自己的清理函数,之前可以用useEffect清理,现在ref有自己的清理函数了。

示例:

js 复制代码
<div
  ref={(ref) => {
    // 当元素从DOM中移除时的引用。
    return () => {
      // ref 清理函数
    };
  }}
/>

4、Context 改动

之前使用 Context 基本就是三步走:创建Context;Provider传递value;后代组件消费value。React19中简化了Context.Provider,可以直接使用Context代替Provider:

示例:

js 复制代码
const ThemeContext = createContext('');

function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );  
}

5、其他改动

1、支持文档元数据

示例:

js 复制代码
export default function Demo ({ props }) {
  return (
    <section>
      <title>{props.title}</title>
      <meta name="name" content="content" />
      <Link rel="test" href="xxx" />
      <meta name="keywords" content={props.keywords} />
      <p>测试内容...</p>
    </section>
  )
}

2、支持样式表

js 复制代码
export default function Demo ({ props }) {
  return (
    <section>
      <link rel="stylesheet" href="xxx" />
      <p>测试内容...</p>
    </section>
  )
}

3、支持异步脚本

js 复制代码
export default function Demo ({ props }) {
  return (
    <section>
      <script async={true} src="xxx" />
      <p>测试内容...</p>
    </section>
  )
}

6、支持自定义元素

在之前的版本中,React会把不认识的props当做attributes来处理,在React19中,此行为改为与自定义元素实例上的属性匹配的 props 被分配为 properties,其他的被分配为attributes。

相关推荐
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte9 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT069 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法
剪刀石头布啊9 小时前
生成随机数,Math.random的使用
前端