React 19 即将迎来大更新!

公众号: 程序员白特,欢迎一起交流学习~

近日, React 团队发布消息称,不会再发布v18.3版本了,而是将重点放在React v19 版本。新版本将推出四个新的 hook,旨在解决 React 中痛点:

  • 数据获取
  • 表单处理

虽然这些 hook 目前作为实验性 APIReact 预览版本中可用,但它们预计将成为 React 19 的稳定功能,但是最终发布React v19之前,API 可能会有所变化。

新的 Hooks 包括:

  • use
  • useFormState
  • useFormStatus
  • useOptimistic

除此之外,还更新了:

  • Async Transitions
  • Form Actions

use

use 是一个实验性 React Hook,它可以让读取类似于 Promisecontext 的资源的值。

javascript 复制代码
const value = use(resource);

官方文档: zh-hans.react.dev/reference/r...

use(Promise)

use 可以在客户端进行"挂起"的 API。可以将一个 promise 传递给它,React 将会在该 promise 解决之前进行挂起。它的基本语法如下:

javascript 复制代码
import { use } from 'react';

function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  // ...
}

下面来看一个简单的例子:

javascript 复制代码
import * as React from 'react';
import { useState, use, Suspense } from 'react';
import { faker } from '@faker-js/faker';

export const App = () => {
  const [newsPromise, setNewsPromise] = useState(() => fetchNews());

  const handleUpdate = () => {
    fetchNews().then((news) => {
      setNewsPromise(Promise.resolve(news));
    });
  };

  return (
    <>
      <h3>
        新闻列表 <button onClick={handleUpdate}>刷新</button>
      </h3>
      <NewsContainer newsPromise={newsPromise} />
    </>
  );
};

let news = [...new Array(4)].map(() => faker.lorem.sentence());

const fetchNews = () =>
  new Promise<string[]>((resolve) =>
    // 使用 setTimeout 模拟数据获取过程setTimeout
    setTimeout(() => {
      news.unshift(faker.lorem.sentence());
      resolve(news);
    }, 1000)
  );

const NewsContainer = ({ newsPromise }) => (
  <Suspense fallback={<p>获取新闻中...</p>}>
    <News newsPromise={newsPromise} />
  </Suspense>
);

const News = ({ newsPromise }) => {
  const news = use<string[]>(newsPromise);
  return (
    <ul>
      {news.map((title, index) => (
        <li key={index}>{title}</li>
      ))}
    </ul>
  );
};

在上面的例子中,每次刷新时,都会先显示"请求中...",请求到数据后进行展示:

点击查看运行效果

官方文档中,关于 有一个警告:

目前尚不支持在不使用固定框架的情况下进行启用 Suspense 的数据获取。实现支持 Suspense 数据源的要求是不稳定的,也没有文档。React 将在未来的版本中发布官方 API,用于与 Suspense 集成数据源。

在新版本中,use 可能就是用于与 Suspense 集成数据源的官方 API。

这个全新的use hook 与其他 Hooks 不同,它可以在循环和条件语句中像 if 一样被调用。这意味着我们可能不再需要依赖像 TanStack Query 这样的第三方库在客户端进行数据获取。然而,这仍需进一步观察,因为 Tanstack Query 的功能远不止解析 Promise 这么简单。

use(Context)

use(Context) 可以用于读取 React Context。它与 useContext 完全相同,只是可以在循环和条件语句(如 if)中调用。

javascript 复制代码
import { use } from 'react';

function HorizontalRule({ show }) {
    if (show) {
        const theme = use(ThemeContext);
        return <hr className={theme} />;
    }
    return false;
}

这将简化某些用例的组件层次结构,因为在循环或条件语句中读取上下文的唯一方式是将组件拆分为两个部分。

从性能方面来看,这也是一个巨大的进步,因为现在可以有条件地跳过组件的重新渲染,即使上下文已经发生了变化。

官方文档: react.dev/reference/r...

useFormState

这个新的hook旨在帮助处理上述描述的异步表单操作功能。调用 useFormState 来访问上次表单提交时的操作返回值。

javascript 复制代码
import { useFormState } from 'react-dom';
import { action } from './action';

function MyComponent() {
    const [state, formAction] = useFormState(action, null);
    // ...
    return <form action={formAction}>{/* ... */}</form>;
}

例如,这可以让您显示表单操作返回的确认或错误信息。

javascript 复制代码
import { useState } from 'react';
import { useFormState } from 'react-dom';

const AddToCartForm = ({ id, title, addToCart }) => {
  const addToCartAction = async (prevState, formData) => {
    try {
      await addToCart(formData, title);
      return 'Added to cart';
    } catch (e) {
      return '无法添加到购物车:该商品已售罄。';
    }
  };

  const [message, formAction] = useFormState(addToCartAction, null);

  return (
    <form action={formAction}>
      <h2>{title}</h2>
      <input type="hidden" name="itemID" value={id} />
      <button type="submit">加入购物车</button>&nbsp;
      {message}
    </form>
  );
};

type Item = {
  id: string;
  title: string;
};

export const App = () => {
  const [cart, setCart] = useState<Item[]>([]);

  const addToCart = async (formData: FormData, title) => {
    const id = String(formData.get('itemID'));
    await new Promise((resolve) => setTimeout(resolve, 1000));
    if (id === '1') {
      setCart((cart: Item[]) => [...cart, { id, title }]);
    } else {
      throw new Error('Unavailable');
    }

    return { id };
  };

  return (
      <AddToCartForm id="2" title="商品" addToCart={addToCart} />
  );
};

点击查看运行效果

注意:useFormState必须从react-dom导入,而不是react。

useFormStatus

useFormStatus 可让您了解父级 <form> 当前是否正在提交或已成功提交。它可以被表单的子组件调用,并返回一个包含以下属性的对象。

javascript 复制代码
const { pending, data, method, action } = useFormStatus();

您可以使用 data 属性来显示用户提交的数据。您也可以显示一个等待状态,例如以下示例中,当表单正在提交时,按钮被禁用。

javascript 复制代码
import { useState } from 'react';
import { useFormStatus } from 'react-dom';

const AddToCartForm = ({ id, title, addToCart }) => {
  const formAction = async (formData) => {
    try {
      await addToCart(formData, title);
    } catch (e) {
      // show error notification
    }
  };

  return (
    <form action={formAction}>
      <h2>{title}</h2>
      <input type="hidden" name="itemID" value={id} />
      <SubmitButton />
    </form>
  );
};

const SubmitButton = () => {
  const { pending } = useFormStatus();
  return (
    <button disabled={pending} type="submit">
      加入购物车
    </button>
  );
};

type Item = {
  id: string;
  title: string;
};

const Cart = ({ cart }: { cart: Item[] }) => {
  if (cart.length == 0) {
    return null;
  }
  return (
    <>
      购物车:
      <ul>
        {cart.map((item, index) => (
          <li key={index}>{item.title}</li>
        ))}
      </ul>
      <hr />
    </>
  );
};

export const App = () => {
  const [cart, setCart] = useState<Item[]>([]);

  const addToCart = async (formData: FormData, title) => {
    const id = String(formData.get('itemID'));
    await new Promise((resolve) => setTimeout(resolve, 1000));
    setCart((cart: Item[]) => [...cart, { id, title }]);

    return { id };
  };

  return (
    <>
      <Cart cart={cart} />
      <AddToCartForm id="2" title="商品" addToCart={addToCart} />
    </>
  );
};

点击查看运行效果

注意:useFormState必须从react-dom导入,而不是react。此外,它仅在父表单使用上述 action 属性时才有效。

useFormState 一起使用,这个钩子将提升客户端表单的用户体验,而不会使您的组件混杂着无用的contexteffects

useOptimistic

useOptimistic hook 使得在执行类似提交表单这样的操作,等待服务器响应时,能够让UI更美观,增强用户体验。

在上面的购物车示例中,我们可以使用此 hookAJAX 调用完成之前显示添加了新商品的购物车:

javascript 复制代码
import { useState, useOptimistic } from 'react';

const AddToCartForm = ({ id, title, addToCart, optimisticAddToCart }) => {
  const formAction = async (formData) => {
    optimisticAddToCart({ id, title });
    try {
      await addToCart(formData, title);
    } catch (e) {
      // show error notification
    }
  };

  return (
    <form action={formAction}>
      <h2>{title}</h2>
      <input type="hidden" name="itemID" value={id} />
      <button type="submit">加入购物车</button>
    </form>
  );
};

type Item = {
  id: string;
  title: string;
};

const Cart = ({ cart }: { cart: Item[] }) => {
  if (cart.length == 0) {
    return null;
  }
  return (
    <>
      购物车:
      <ul>
        {cart.map((item, index) => (
          <li key={index}>{item.title}</li>
        ))}
      </ul>
      <hr />
    </>
  );
};

export const App = () => {
  const [cart, setCart] = useState<Item[]>([]);

  const [optimisticCart, optimisticAddToCart] = useOptimistic<Item[], Item>(
    cart,
    (state, item) => [...state, item]
  );

  const addToCart = async (formData: FormData, title) => {
    const id = String(formData.get('itemID'));
    await new Promise((resolve) => setTimeout(resolve, 1000));
    setCart((cart: Item[]) => [...cart, { id, title }]);

    return { id };
  };

  return (
    <>
      <Cart cart={optimisticCart} />
      <AddToCartForm
        id="2"
        title="商品"
        addToCart={addToCart}
        optimisticAddToCart={optimisticAddToCart}
      />
    </>
  );
};

点击查看运行效果

Async Transitions

ReactTransition API 允许您在不阻塞用户界面的情况下更新状态。例如,它允许您在用户改变主意时取消先前的状态更改。

这个想法是将状态更改包装在一个 startTransition 调用中。

以下示例展示了使用此 Transitions API 的选项卡导航。单击"Posts",然后立即单击"contact"。请注意,这会中断"Posts"的缓慢渲染。 "联系人"选项卡立即显示。由于此状态更新被标记为转换,因此缓慢的重新渲染不会冻结用户界面。

javascript 复制代码
import { useState, useTransition } from 'react';
import TabButton from './TabButton';
import AboutTab from './AboutTab';
import PostsTab from './PostsTab';
import ContactTab from './ContactTab';

export function App() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }

  return (
    <>
      <TabButton isActive={tab === 'about'} onClick={() => selectTab('about')}>
        About
      </TabButton>
      <TabButton isActive={tab === 'posts'} onClick={() => selectTab('posts')}>
        Posts (slow)
      </TabButton>
      <TabButton
        isActive={tab === 'contact'}
        onClick={() => selectTab('contact')}
      >
        Contact
      </TabButton>
      <hr />
      {tab === 'about' && <AboutTab />}
      {tab === 'posts' && <PostsTab />}
      {tab === 'contact' && <ContactTab />}
    </>
  );
}

点击查看运行效果

useTransition 钩子函数已经在 React 18.2 中可用。React 19 中的新功能是,现在您可以将异步函数传递给 startTransition,并由 React 等待以开始过渡。

这对于通过 AJAX 调用提交数据并在过渡中呈现结果非常有用。过渡挂起状态始于异步数据提交。它已经用于上面描述的表单操作功能。这意味着 React 调用用 startTransition 包装的 <form action> 处理程序,因此不会阻塞当前页面。

Form Actions

这个新功能使您能够将函数传递给 <form>action 属性。当表单提交时,React 会调用这个函数:

javascript 复制代码
<form action={handleSubmit} />

请注意,在 React 18 中,如果您添加了一个带有 action 属性的<form> 标签,您将收到以下警告:

警告:在 <form> 标签上的 action 属性的值无效。要么将其从元素中删除,要么传递一个字符串或数字值以使其保留在 DOM 中。

然而,在 React 19 中,您可以像这样编写一个表单:

javascript 复制代码
import { useState } from 'react';

const AddToCartForm = ({ id, title, addToCart }) => {
  const formAction = async (formData) => {
    try {
      await addToCart(formData, title);
    } catch (e) {
      // show error notification
    }
  };

  return (
    <form action={formAction}>
      <h2>{title}</h2>
      <input type="hidden" name="itemID" value={id} />
      <button type="submit">加入购物车</button>
    </form>
  );
};

type Item = {
  id: string;
  title: string;
};

const Cart = ({ cart }: { cart: Item[] }) => {
  if (cart.length == 0) {
    return null;
  }
  return (
    <>
      购物车内容:
      <ul>
        {cart.map((item, index) => (
          <li key={index}>{item.title}</li>
        ))}
      </ul>
      <hr />
    </>
  );
};

export const App = () => {
  const [cart, setCart] = useState<Item[]>([]);

  const addToCart = async (formData: FormData, title) => {
    const id = String(formData.get('itemID'));
    // 模拟一个 AJAX 请求
    await new Promise((resolve) => setTimeout(resolve, 1000));
    setCart((cart: Item[]) => [...cart, { id, title }]);

    return { id };
  };

  return (
      <AddToCartForm
        id="2"
        title="商品"
        addToCart={addToCart}
      />
  );
};

点击查看运行效果

addToCart 函数不是一个服务器端操作。它在客户端被调用,可以是一个异步函数。

这将极大地简化在 React 中处理 AJAX 表单的方式,例如在搜索表单中。但是,这可能并不足以摆脱第三方库,比如 React Hook Form,它不仅仅处理表单提交(还包括验证、副作用等)。

提示:您可能会发现上面的示例中存在一些可用性问题(提交按钮在提交时未禁用,缺少确认消息,购物车更新较晚)。幸运的是,更多的钩子即将推出,以帮助处理这种情况。

官方文档: react.dev/reference/r...

相关推荐
光影少年19 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_20 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891122 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾24 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking24 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu26 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym30 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫32 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫36 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat37 分钟前
前端性能优化2
前端