React18 和 19 新特性结合看

useTransition Hook 和 Actions

使用异步过渡(useTransition)的函数被称为 Actions(函数命名应以 Action 结尾)

js 复制代码
// 使用 Actions 中的待定状态  
const [isPending, startTransition] = useTransition()

const handleSubmit = () => {
    startTransition(async () => {
        // 请求接口
    })
}
return (
    <button onClick={handleSubmit} disabled={isPending}>提交</button>
)

Actions 做了什么?自动管理数据提交

  • 待定状态:提供待定状态功能类似于 loading
  • 乐观更新:提供 useOptimistic Hook
  • 错误处理
  • 表单:和 form 元素结合,可以将 Actions 传递给 form 的 action 和 formAction 属性(官方例子中,普通函数传递给表单会被自动包裹在 startTransition 中

引申出 useOptimistic Hook

目的是为了消除网络等待的延迟感,而不是限制函数的触发频率,不可理解为防抖或节流。

  • 防抖:多次触发,"最后一次"操作达到时间间隔后执行
  • 节流:多次触发,一段时间间隔执行一次
  • useOptimistic:先前端更新最新值,让用户感觉瞬间完成了操作

useTransition 和 useOptimistic 都可以获取 pending 状态,区别在于前者会提供 startTransition 函数,后者可以在任何异步过渡中使用。

useOptimistic Hook 可以接收多个 values 和 reducer

js 复制代码
const [optimisticState, updateOptimistic] = useOptimistic(
    { isFollowing: user.isFollowing, followerCount: user.followerCount },
    (current, isFollowing) => ({
      isFollowing,
      followerCount: current.followerCount + (isFollowing ? 1 : -1)
    })
 );

使用 useOptimistic 最好使用 reducer 相对更新而不是绝对赋值

js 复制代码
// May show stale data if state changes during Action  
const [optimistic, setOptimistic] = useOptimistic(count);  
setOptimistic(5); // Always sets to 5, even if count changed  
// Better: relative updates handle state changes correctly  
const [optimistic, adjust] = useOptimistic(count, (current, delta) => current + delta);  
adjust(1); // Always adds 1 to whatever the current count is

useDeferredValue Hook

在新内容加载期间显示旧内容,同时表明旧内容已过时

js 复制代码
import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}

延迟渲染 UI 的某些部分,比如一个文本框和一个列表,不希望列表的渲染阻碍输入框的输入,这并没有让 SlowList 的重新渲染变快,只是告诉 React 可以将列表的重新渲染优先级降低,不阻碍按键输入。

js 复制代码
import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

// 组件需包裹在 memo 里
import { memo } from 'react';

const SlowList = memo(function SlowList({ text }) {
  // 仅打印一次。实际的减速是在 SlowItem 组件内部。
  console.log('[ARTIFICIALLY SLOW] Rendering 250 <SlowItem />');

  let items = [];
  for (let i = 0; i < 250; i++) {
    items.push(<SlowItem key={i} text={text} />);
  }
  return (
    <ul className="items">
      {items}
    </ul>
  );
});

function SlowItem({ text }) {
  let startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // 每个 item 暂停 1ms,模拟极其缓慢的代码
  }

  return (
    <li className="item">
      Text: {text}
    </li>
  )
}

export default SlowList;

引申出 Suspense 组件

当内容正在加载时显示后备方案,只有使用了 Suspense 数据源(支持 Suspense 的框架 Relay 和 Next.js;使用 Lazy 懒加载组件;使用 use 读取 Promise 值)才会激活 Suspense 组件。

允许嵌套多个 Suspense 组件创建一个加载序列逐步加载内容

js 复制代码
import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<BigSpinner />}>
        <Biography artistId={artist.id} />
        <Suspense fallback={<AlbumsGlimmer />}>
          <Panel>
            <Albums artistId={artist.id} />
          </Panel>
        </Suspense>
      </Suspense>
    </>
  );
}

function BigSpinner() {
  return <h2>🌀 Loading...</h2>;
}

function AlbumsGlimmer() {
  return (
    <div className="glimmer-panel">
      <div className="glimmer-line" />
      <div className="glimmer-line" />
      <div className="glimmer-line" />
    </div>
  );
}

加载序列将会是:

  1. 如果 Biography 没有加载完成,BigSpinner 会显示在整个内容区域的位置。
  2. 一旦 Biography 加载完成,BigSpinner 会被内容替换。
  3. 如果 Albums 没有加载完成,AlbumsGlimmer 会显示在 Albums 和它的父级 Panel 的位置。
  4. 最后,一旦 Albums 加载完成,它会替换 AlbumsGlimmer

和 useDeferredValue 组合使用也可实现之前使用了 memo 组件例子的同样效果

相关推荐
Java小卷2 小时前
低代码表单联动配置:从节点驱动到数据驱动
前端·低代码
DianSan_ERP2 小时前
WMS接入电商平台自动化履约实战:一张订单从平台到出库的接口时序设计
java·前端·网络·数据库·安全·架构·自动化
莪_幻尘2 小时前
手把手书写你的第一个 AI Agent:当 Skill 有了记忆、角色和主动性
前端·agent
原则猫3 小时前
设计模式
前端
光影少年3 小时前
react navite环境与工程化
前端·react native·react.js
PedroQue993 小时前
v2.6.0 新增全局返回守卫与 iOS 侧滑拦截,补齐跨端路由拦截能力
前端·uni-app
lichenyang4533 小时前
从「房间」到「实时通知」:用 NestJS + Socket.IO 实现团队邀请的完整工程实践
前端·后端
ly76893 小时前
Vue 3 从入门到工程实践:基础语法、组件化、路由、状态管理与项目部署
前端·javascript·vue.js
掘金酱4 小时前
TRAE Work 实战帮征文 | 获奖名单公示
前端·人工智能·后端