我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

乐观更新这个事情,其实存在着一些问题。主要体现在因为乐观更新,就不考虑服务器提交等待这个问题。界面上的各种按钮及输入框,是没有disabled这种说法的。想想看,这种提交等待的传统做法,是不是和乐观更新的思想冲突呢?短时间内大量提交的话,消息的时间轴存在混乱和重复的问题。

苏南大叔:React19新特性,乐观更新useOptimistic()范例代码优化 - 转换重绘useTransition
React19新特性,乐观更新useOptimistic()范例代码优化(图5-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0create-react-app@5.0.1react@19.0.0。本文视图解决useOptimistic()官方范例里面的消息的时间轴重复和混乱的问题。

前文回顾

本文代码的useOptimistic()范例代码,来自下面的这篇文章。

主要实现的功能就是:面对大概率成功的接口数据交互,提前假设数据正常返回,界面迅速得到渲染,提高用户体验。这个问题,被乱蒿羊毛的结果,就是极短时间内多次触发提交的话。数据混乱和重复的问题。

useTransition()

试图通过useTransition()来解决上述问题,效果也并不明显。参考文章:

使用useTransition(),来获得pending状态和一个可过渡的逻辑包装组合。被useTransition()包裹的代码端,具有较低的优先级,如果有界面交互用户输入的高优先级任务的时候,这些低优先级的任务会被延迟执行。

考虑到和乐观更新的效果相抵触,代码中并没有对pending状态进行使用。仅仅保留了formAction中的startTransition()

import { useOptimistic, useTransition } from "react";
const [isPending, startTransition] = useTransition();
async function formAction(formData) {
  startTransition(async () => {
    //...
  });
}

其实这个useTransition()效果,并不明显。似乎更多的是出于心理安慰的作用。

增加消息id

这里对每条消息增加了一个key,其实也可以理解为自增id。主要想利用这个自增值来确定消息的身份和先后顺序。

let message_id = useRef(messages.length);
async function formAction(formData) {
  startTransition(async () => {
    let message = {
      text: formData.get("message"),
      key: message_id.current.toString(),
    };
    message_id.current++;
    //...
  });
}

苏南大叔:React19新特性,乐观更新useOptimistic()范例代码优化 - 自增id
React19新特性,乐观更新useOptimistic()范例代码优化(图5-2)

解决顺序混乱的问题

顺序为什么会混乱,主要原因在于苏南大叔修改的代码里面,服务器端返回的时间做了随机,这也更贴近现实的情况。如果服务器返回时间都一致的话,消息顺序并不会“混乱”。

解决方案的原理,苏南大叔是利用界面格式化的时候,对"乐观数据"进行了排序(`.sort())。

{optimisticMessages
  .sort((a, b) => a.key - b.key)
  .map((message, index) => (
    <div key={index}>
      {message.key}.{message.text}
      {!!message.sending && <small>(发送中……)</small>}
    </div>
  ))}

苏南大叔:React19新特性,乐观更新useOptimistic()范例代码优化 - 解决排序问题
React19新特性,乐观更新useOptimistic()范例代码优化(图5-3)

解决数据重复的问题

据仔细观察,重复的数据都是瞬时存在的。两条同一个key的数据,处于两个不同的sending状态。如果调试useOptimistic()钩子的第二个参数的话。会发现这个合并数据的逻辑被执行了两次。所以,问题就出在这个“两次”上。

修改完的逻辑如下:

const [optimisticMessages, addOptimisticMessage] = useOptimistic(
  messages,
  (state, newMessage) => {
    if (state.some((a) => a.key == newMessage.key)) {
      return state;
    }
    return [
      ...state,
      {
        key: newMessage.key,
        text: newMessage.text,
        sending: true,
      },
    ];
  }
);

苏南大叔:React19新特性,乐观更新useOptimistic()范例代码优化 - 解决重复问题
React19新特性,乐观更新useOptimistic()范例代码优化(图5-4)

完整代码

完整代码如下:

import { useOptimistic, useTransition, useState, useRef } from "react";

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

async function serverAPI(message) {
  let tmp = getRandomInt(1, 8) * 1000;
  await new Promise((res) => setTimeout(res, tmp));
  return message; // 这个return很重要,被合并到真实state里面,进而影响乐观state
}

function Main({ messages, addRealMessageFn }) {
  const [isPending, startTransition] = useTransition();

  const formRef = useRef(null);
  let message_id = useRef(messages.length);

  async function formAction(formData) {
    startTransition(async () => {
      let message = {
        text: formData.get("message"),
        key: message_id.current.toString(),
      };
      message_id.current++;
      addOptimisticMessage(message);
      formRef.current.reset();
      await addRealMessageFn(message);
    });
  }
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => {
      if (state.some((a) => a.key == newMessage.key)) {
        return state;
      }
      return [
        ...state,
        {
          key: newMessage.key,
          text: newMessage.text,
          sending: true,
        },
      ];
    }
  );
  return (
    <>
      <form action={formAction} ref={formRef}>
        <input
          type="text"
          name="message"
          placeholder="你好!"
          defaultValue={"sunan"}
        />
        <button type="submit">发送</button>
      </form>
      {optimisticMessages
        .sort((a, b) => a.key - b.key)
        .map((message, index) => (
          <div key={index}>
            {message.key}.{message.text}
            {!!message.sending && <small>(发送中……)</small>}
          </div>
        ))}
    </>
  );
}

export default function App() {
  const [messages, setMessages] = useState([
    { text: "你好,苏南大叔", key: 0 },
    { text: "hola,Uncle Sunan", key: 1 },
  ]);
  async function addRealMessageFn(message) {
    const okMessage = await serverAPI(message);
    setMessages((messages) => [
      ...messages,
      { text: okMessage.text, key: okMessage.key },
    ]);
  }
  return <Main messages={messages} addRealMessageFn={addRealMessageFn} />;
}

苏南大叔:React19新特性,乐观更新useOptimistic()范例代码优化 - 运行效果截图
React19新特性,乐观更新useOptimistic()范例代码优化(图5-5)

结语

在本文的代码里面,对于“乐观更新”这个思想,又增加了新的认识。更新可以保持乐观,但是也带来了一定的数据不可靠性。所以,双刃剑。

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react