react19的use和Suspense:重复请求

105 阅读2分钟

1.捕获promise

deepseek:ReactSuspense 通过一种特殊的机制来捕获 Promise,从而实现异步操作的声明式处理。
那么我想看看是怎么回事
import { Suspense } from "react";

// 原先放MyComponent里的,但会不断更新
const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Hello, world!");
  }, 2000);
});

const MyComponent =async () => {
  await myPromise.then((result) => {
    console.log(result);
  });
  return <div>Hello, world!</div>;
};

function App() {
  return (
      <Suspense fallback={<div>Loading...</div>}>
       <MyComponent />
      </Suspense>
  );
}

export default App;
实现了效果,页面先Loading...,再Hello, world!
但是有警告和报错
// 异步客户端组件
<MyComponent> is an async Client Component. Only Server Components can be async at the moment. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

// 需要缓存promise
App.tsx:21 
 A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.

2.useEffect

我经常在useEffect里获取数据并赋值到状态
就想放useEffect里
const MyComponent = async () => {
  const proFun = async () => {
    await myPromise
      .then((result) => {
        console.log(result);
      })
  };
  useEffect(() => {
    proFun();
  }, []);
  return <div>Hello, world!</div>;
};
效果丢失
https://react.dev/reference/react/Suspense
官网说:Suspense does not detect when data is fetched inside an Effect or event handler

3.use

react19新增api
实现效果,无警告无报错
import { Suspense, use } from "react";

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Hello, world!");
  }, 2000);
});

const MyComponent = () => {
  // 如何解决重复请求的问题?
  const data: any = use(myPromise);
  return <div>{data}</div>;
};

function App() {
  return (
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
  );
}

export default App;
// 运行机制
use(promise) {
    const hook = mountWorkInProgressHook();
    if (promise.status === 'pending') {
      throw promise; // 触发Suspense
    }
    return promise.result;
  },

4.重复请求

例子

1function MyComponent() {
  // ❌ 错误:在渲染函数中直接创建新的 Promise 并传递给 use()
  // 每次渲染都会发起新的请求,导致重复请求甚至无限循环
  const data = use(fetch('/api/data')); // fetch 返回一个 Promise

  return <div>{data}</div>;
}
2const MyComponent = () => {
  const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Hello, world!");
    }, 2000);
  });
  const data: any = use(myPromise);
  return <div>{data}</div>;
};

function App() {
  return (
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
  );
}
3function MyComponent() {
  // ❌ 错误:在渲染函数中直接创建新的 Promise 并传递给 use()
  // 每次渲染都会发起新的请求
  const data = use(axios.get('/api/data')); // axios.get 返回一个 Promise

  return <div>{data}</div>;
}

测试axios

// 正常返回res

const MyComponent = () => {
  const getCapFunc = async () => {
    const res = await getCaptchaReq({ email: "8100580@qq.com", type: "register" });
    console.log(res);
  };
  useEffect(() => {
    getCapFunc();
  }, []);
  return <div>{data}</div>;
};
// 确实出现了大量重复请求

const MyComponent = () => {
  const data: any = use(getCaptchaReq({ email: "8100580@qq.com", type: "register" }));
  return <div>{data.data}</div>;
};
// 如果用SWR、React Query/TanStack Query库,感觉又麻烦了
// 事情进行到这里,我感觉还是算了,太麻烦,为了个use不仅需要Suspense和Error Boundary,还要额外缓存库
// 将请求放到父组件,没有重复请求
// 但是我们的参数一般都是在MyComponent组件中提供
// 有一种情况,父组件传一个id给子组件,子组件根据id获取数据
// 那么父组件可以提前创建请求promise,在子组件中使用use取到数据
type MyComponentProps = {
  promise: Promise<any>;
};

const MyComponent = ({ promise }: MyComponentProps) => {
  const data: any = use(promise);
  return <div>{JSON.stringify(data)}</div>;
};

function App() {
  const captchaPromise = getCaptchaReq({
    email: "8100580@qq.com",
    type: "register",
  });
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyComponent promise={captchaPromise} />
    </Suspense>
  );
}