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);
    }
  };
相关推荐
wuhen_n1 小时前
CSS元素动画篇:基于页面位置的变换动画
前端·css·html·css3·html5
sql123456789111 小时前
前端——CSS1
前端
Nueuis1 小时前
微信小程序分页和下拉刷新
服务器·前端·微信小程序
接着奏乐接着舞1 小时前
阿里qiankun微服务搭建
微服务·架构·前端框架
小白64022 小时前
前端性能优化(实践篇)
前端·性能优化
小彭努力中2 小时前
9.Three.js中 ArrayCamera 多视角相机详解+示例代码
开发语言·前端·javascript·vue.js·数码相机·ecmascript·webgl
朝阳392 小时前
Electron Forge【实战】桌面应用 —— 将项目配置保存到本地
前端·javascript·electron
若愚67922 小时前
Tauri 跨平台开发指南及实战:用前端技术征服桌面应用(合集-万字长文)
前端·vue.js·rust·gitcode
不秃的开发媛3 小时前
前端技术Ajax实战
前端·javascript·ajax
IT瘾君3 小时前
JavaWeb:后端web基础(Tomcat&Servlet&HTTP)
前端·servlet·tomcat