编辑
2025-09-04
技术
00
请注意,本文编写于 102 天前,最后修改于 102 天前,其中某些信息可能已经过时。

目录

useOptimistic
useOptimistic && startTransiton
手搓一个乐观更新!

前几天了解了Nextjs做的prefetch的一些原理,然后看到了一个很好玩的hook,也就是乐观更新这个概念(我觉得是挺有用的(bushi))

useOptimistic

我们先看一下官方给的解释
useOptimistic 是一个 React Hook,它允许你在进行异步操作时显示不同 state。它接受 state 作为参数,并返回该 state 的副本,在异步操作(如网络请求)期间可以不同。你需要提供一个函数,该函数接受当前 state 和操作的输入,并返回在操作挂起期间要使用的乐观状态。

这个状态被称为“乐观”状态是因为通常用于立即向用户呈现执行操作的结果,即使实际上操作需要一些时间来完成。

tsx
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);

也就是说,当页面进行更新(如网络请求,异步操作等需要消耗一定时间的操作)时,我们可以假设它更新是成功的(你得自信,自信使你的页面快乐),并且立刻向页面呈现状态

tsx
import { useOptimistic } from 'react'; function AppContainer() { const [optimisticState, addOptimistic] = useOptimistic( state, // 更新函数 (currentState, optimisticValue) => { // 使用乐观值 // 合并并返回新 state } ); }

其中

  • state : 初始时和没有挂起操作时要返回的值,其实就是你的「真实数据」。
  • updateFn : 一个更新函数 (currentState, optimisticValue) => newState,它会在你调用 addOptimistic() 时运行,把「真实数据」和「乐观数据」合并,得到一个临时的新 state。

失败是小概率事件,但成功是大概率事件。 与其等成功的返回,不如直接假设成功。

相信聪明的你已经想到了,其实我们的使用场景也很简单,我目前认为最合适,最适配的就是类似于评论区之类的东西,我们来写一个实现一键三连的小组件实战一下

tsx
"use client"; import { useOptimistic } from "react"; import { Heart, Coins, Bookmark } from "lucide-react"; import { likeVideo, coinVideo, favoriteVideo } from "@/app/actions"; type Stats = { likes: number; coins: number; favorites: number; }; type Props = { initial: Stats; // Initial counts from server videoId: string; // ID of the current video }; /** * @example * ```tsx * <BilibiliActions * videoId="abc123" * initial={{ likes: 120, coins: 50, favorites: 33 }} * /> * ``` */ export default function BilibiliActions({ initial, videoId }: Props) { /** * useOptimistic hook * * @param state - The current stats (likes, coins, favorites) * @param action - The action to apply (which field to increment) * @returns Updated stats with optimistic increment applied */ const [optimisticStats, updateOptimistic] = useOptimistic( initial, (state: Stats, action: { type: keyof Stats }) => { return { ...state, [action.type]: state[action.type] + 1 }; } ); /** * Handles user interaction and sends server request. * First updates UI optimistically, then calls the corresponding server action. * * @param type - Which stat to increment ("likes" | "coins" | "favorites") */ async function handleAction(type: keyof Stats) { updateOptimistic({ type }); switch (type) { case "likes": await likeVideo(videoId); break; case "coins": await coinVideo(videoId); break; case "favorites": await favoriteVideo(videoId); break; } } return ( <div className="flex gap-6 items-center"> {/* Like button */} <button onClick={() => handleAction("likes")} className="flex items-center gap-1 text-gray-600 hover:text-pink-500 transition" > <Heart className="w-5 h-5" /> <span className="text-sm">{optimisticStats.likes}</span> </button> {/* Coin button */} <button onClick={() => handleAction("coins")} className="flex items-center gap-1 text-gray-600 hover:text-yellow-500 transition" > <Coins className="w-5 h-5" /> <span className="text-sm">{optimisticStats.coins}</span> </button> {/* Favorite button */} <button onClick={() => handleAction("favorites")} className="flex items-center gap-1 text-gray-600 hover:text-blue-500 transition" > <Bookmark className="w-5 h-5" /> <span className="text-sm">{optimisticStats.favorites}</span> </button> </div> ); }

你也发现了,乐观更新的好处还有之一:也就是后端不再返回数据,只需要一个成功or失败就可以了

useOptimistic && startTransiton

首先我们要知道transition是干什么的:

transiton将会标记一个state为异步非阻塞状态(也就是说这一次的更新并不会阻塞渲染),比如你的某个组件可能会进行高频更新(假如用户反复点击点赞按钮,状态并不会第一时间更新,而是等这一阶段的transition结束之后再更新state)

tsx
"use client"; import { useOptimistic, useTransition } from "react"; import { startTransition as s } from "react"; // 也可用 useTransition import { likeVideo } from "@/app/actions"; export function LikeWithOptimistic({ initial, videoId }: { initial: number; videoId: string }) { const [likes, setLikes] = useState(initial); const [optimisticLikes, addOptimistic] = useOptimistic(likes, (s, step: number) => s + step); const [isPending, startTransition] = useTransition(); async function onLike() { addOptimistic(1); // 立即+1 // 低优先级刷新真实数据(例如从服务端返回最新计数) startTransition(async () => { const next = await likeVideo(videoId); // server action setLikes(next); }); } return ( <button onClick={onLike} className="btn"> 👍 {optimisticLikes} {isPending && <small className="ml-2 text-gray-500">同步中</small>} </button> ); }

手搓一个乐观更新!

知道原理了,我们直接手搓一个! 其实我们仔细思考一下无非就是

  1. 更新乐观state到state上
  2. 如果传入state和乐观state一致时候,保持不变
  3. 如果传入state为空或者不一致时候,state进行回滚

(还没实现 主包困了明天再写)

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:MapleCity

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!