React 19 用到的新特性

最近在关注 React 19 的变化,比如 use、ref、useOptimistic 乐观更新等。

  • use:请求接口,请求等待中,显示 loading,请求成功,展示数据。接收一个 Promise,会阻塞 render 继续渲染,需要配套 <Suspense> 处理 loading,配套 <ErrorBoundary> 来处理异常。
  • ref 支持返回一个 cleanup 函数,在组件卸载时会调用该函数。
  • useOptimistic 乐观更新:收藏点赞的场景,用户触发后,立即更新 UI,如果请求失败后,再回滚 UI。

use

use 是 React 19 新增的一个特性,支持处理 Promise 和 Context。

假如我们要实现这样一个需求:请求接口数据,请求过程中,显示 loading,请求成功,展示数据。

以前我们可能会这样写代码

javascript 复制代码
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>;
}

通过 use 我们可以把代码改造成下面这样

javascript 复制代码
export default function ReactUseDemo() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <ChildCompont />
    </Suspense>
  );
}

function ChildCompont() {
  const data = use(getData());
  return <div>{data}</div>;
}

use 接收一个 Promise,会阻塞 render 继续渲染,通常需要配套 Suspense 处理 loading 状态,需要配套 ErrorBoundary 来处理异常状态。

另外 use 也支持接收 Context,类似之前的 useContext,但比 useContext 更灵活,可以在条件语句和循环中使用。

javascript 复制代码
function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  const theme = use(ThemeContext);
  ......
}

use 的使用有一些注意事项

  • 需要在组件或 Hook 内部使用
  • use 可以在条件语句(比如 if)或者循环(比如 for)里面调用

ref

在之前,父组件传递 ref 给子组件,子组件如果要消费,则必须通过 forwardRef 来消费。

javascript 复制代码
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 开始,不需要使用 forwardRef 了,ref 可以作为一个普通的 props 了。

javascript 复制代码
export const Input = ({ ref }) => {
  return <input ref={ref} />;
};

ref 支持返回 cleanup 函数

ref 支持返回一个 cleanup 函数,在组件卸载时会调用该函数。

javascript 复制代码
<input
  ref={(ref) => {
    // ref created

    // NEW: return a cleanup function to reset
    // the ref when element is removed from DOM.
    return () => {
      // ref cleanup
    };
  }}
/>

useOptimistic 乐观更新

乐观更新是一种常见的体验优化手段,在发送异步请求之前,我们默认请求是成功的,让用户立即看到成功后的状态。

先来看看官方提供的例子:提交表单更新 name,可以立即将新的 name 更新到 UI 中。请求成功则 UI 不变,请求失败则 UI 回滚。

javascript 复制代码
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>
  );
}

useOptimistic 用来维护临时状态,保证 UI 的乐观更新。

javascript 复制代码
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);

返回参数含义:

  • optimisticState:乐观更新的状态,UI 上应该始终消费这个状态。默认等于真正的 state。
  • addOptimistic:更新 optimisticState,可以通过 updateFn 指定更新逻辑

传入参数含义:

  • state:真正的状态
  • updateFn(currentState, optimisticValue) => newOptimisticState,调用 addOptimistic 的时候会通过这个函数生成新的 optimisticState

异步函数执行结束后,无论是成功还是失败,optimisticName 都会重置成和最新 state 一样。

关于乐观更新,我在日常开发中,经常会用到。经典的场景是收藏点赞场景,用户触发后,立即更新 UI,如果请求失败后,再回滚 UI。

javascript 复制代码
function LikeDemo() {
  const [like, setLike] = useState(false);
  const [pending, startTransition] = useTransition();
  const [optimisticLike, setOptimisticLike] = useOptimistic(like);

  const handleLike = () => {
    const targetLike = !like;
    startTransition(async () => {
      try {
        setOptimisticLike(targetLike);
        await updateLike(like);
        setLike(targetLike);
      } catch (e) {
        console.error(e);
      }
    });
  };

  return (
    <div>
      <div onClick={handleLike}>{optimisticLike ? "收藏" : "未收藏"}</div>
    </div>
  );
}

其实我觉得这个 Hook 没多大用,我普通代码也可以实现这个乐观更新,而且更简单。

javascript 复制代码
  const [like, setLike] = useState(false);

  const handleLike = async () => {
    try {
      setLike((s) => !s);
      await updateLike(like);
    } catch (e) {
      setLike((s) => !s);
    }
  };
相关推荐
张彦峰ZYF10 分钟前
一键启动多个 Chrome 实例并自动清理的 Bash 脚本分享!
前端·chrome·bash
浩星1 小时前
vue3+uniapp中使用高德地图实现撒点效果
前端·vue.js·uni-app
JamSlade1 小时前
React 个人笔记 Hooks编程
前端·javascript·笔记·react.js
就叫飞六吧1 小时前
html文件cdn一键下载并替换
前端·python·html
Allen Bright1 小时前
【HTML-3】HTML 中的水平线与换行:基础元素详解
前端·html
纸包鱼最好吃1 小时前
javaweb-html
前端·html
程序二次开发1 小时前
html,js获取扫码设备的输入内容
前端·javascript·html
鲁Q同志2 小时前
vue项目启动报错(node版本与Webpack)
前端·vue.js·webpack
Allen Bright2 小时前
【HTML-5】HTML 实体:完整指南与最佳实践
前端·html
Favour722 小时前
根据当前日期计算并选取上一个月和上一个季度的日期范围,用于日期控件的快捷选取功能
前端·vue.js·elementui