最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React Suspense 異步渲染完全指南

 更新時(shí)間:2026年03月18日 09:13:21   作者:兆子龍  
React Suspense為異步數(shù)據(jù)獲取提供了一種全新的編程范式,本文主要介紹了React Suspense異步渲染完全指南,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、Suspense 是什么

React Suspense 是 React 16.6 引入的一個(gè)特性,它讓你在組件等待異步數(shù)據(jù)時(shí)顯示一個(gè)降落的 UI,同時(shí)保持良好的用戶體驗(yàn)。簡單來說,Suspense 允許你聲明式地處理組件的「加載狀態(tài)」,而無需在每個(gè)組件中手動(dòng)編寫 loading 邏輯。

在 Suspense 出現(xiàn)之前,如果你有一個(gè)組件需要從 API 獲取數(shù)據(jù),你通常需要這樣做:在組件內(nèi)部維護(hù) loading 狀態(tài),數(shù)據(jù)加載時(shí)顯示 loading,數(shù)據(jù)返回后渲染內(nèi)容。這種方式的問題在于,每個(gè)需要異步數(shù)據(jù)的組件都要重復(fù)類似的代碼,而且組件之間的加載狀態(tài)難以統(tǒng)一管理。

Suspense 的核心思想是:讓組件「暫?!逛秩?,直到它需要的數(shù)據(jù)準(zhǔn)備好。這與傳統(tǒng)的「先渲染再填充數(shù)據(jù)」的模式完全不同。在 Suspense 的世界里,組件聲明它需要什么數(shù)據(jù),React 負(fù)責(zé)在數(shù)據(jù)準(zhǔn)備好之前保持組件的「暫?!?fàn)顟B(tài),并渲染一個(gè) fallback UI。

二、Suspense 的基本用法

2.1 簡單示例

Suspense 的使用非常簡單,只需要兩個(gè)部分:一個(gè)或多個(gè) <Suspense> 組件,以及它們包裝的需要異步加載的子組件。

import { Suspense } from 'react';

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<Loading />}>
        <Comments />
      </Suspense>
    </div>
  );
}

function Loading() {
  return <div>Loading...</div>;
}

function Comments() {
  // Comments 組件可能需要異步獲取數(shù)據(jù)
  const comments = useQuery(commentsQuery);
  return (
    <ul>
      {comments.map(comment => (
        <li key={comment.id}>{comment.text}</li>
      ))}
    </ul>
  );
}

在這個(gè)例子中,當(dāng) Comments 組件正在加載數(shù)據(jù)時(shí),用戶會看到 <Loading /> 組件。一旦數(shù)據(jù)準(zhǔn)備好,React 會自動(dòng)用 Comments 的實(shí)際內(nèi)容替換掉 loading UI。

2.2 多個(gè) Suspense 邊界

你可以嵌套使用 Suspense,為不同部分的 UI 提供獨(dú)立的加載狀態(tài):

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<PageSkeleton />}>
        <MainContent />
      </Suspense>
    </div>
  );
}

function MainContent() {
  return (
    <>
      <Suspense fallback={<UserInfoSkeleton />}>
        <UserInfo />
      </Suspense>
      <Suspense fallback={<PostsSkeleton />}>
        <UserPosts />
      </Suspense>
      <Suspense fallback={<CommentsSkeleton />}>
        <RecentComments />
      </Suspense>
    </>
  );
}

這種嵌套結(jié)構(gòu)的好處是:頁面的不同部分可以獨(dú)立加載和渲染。當(dāng)用戶信息正在加載時(shí),帖子和評論部分可以同時(shí)顯示自己的 loading 狀態(tài),而不需要等待所有數(shù)據(jù)都準(zhǔn)備好。

2.3 Suspense 的 props

<Suspense> 組件接受兩個(gè)重要的 props:

  • fallback:在子組件正在「掛起」時(shí)渲染的 React 元素。這可以是一個(gè)簡單的 loading 文本,也可以是一個(gè)復(fù)雜的動(dòng)畫組件。
  • maxDuration(實(shí)驗(yàn)性):指定 fallback 應(yīng)該顯示的最長時(shí)間。如果數(shù)據(jù)在指定時(shí)間內(nèi)準(zhǔn)備好,React 會直接渲染子組件而不顯示 fallback。這對于避免 loading 閃爍很有幫助。
<Suspense 
  fallback={<LoadingSpinner />}
  maxDuration={300}
  <HeavyComponent />
</Suspense>

三、Suspense 與數(shù)據(jù)獲取

3.1 傳統(tǒng)數(shù)據(jù)獲取的問題

在深入 Suspense 之前,讓我們回顧一下傳統(tǒng) React 數(shù)據(jù)獲取的方式,以及它們?yōu)槭裁床粔蚶硐搿?/p>

類組件時(shí)代的做法:

class UserProfile extends React.Component {
  state = {
    user: null,
    loading: true,
    error: null
  };

  async componentDidMount() {
    try {
      this.setState({ loading: true });
      const user = await fetchUser(this.props.userId);
      this.setState({ user, loading: false });
    } catch (error) {
      this.setState({ error, loading: false });
    }
  }

  render() {
    const { user, loading, error } = this.state;
    
    if (loading) return <Loading />;
    if (error) return <ErrorPage error={error} />;
    
    return <UserCard user={user} />;
  }
}

函數(shù)組件 + Hooks 的做法:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetchUser(userId)
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <Loading />;
  if (error) return <ErrorPage error={error} />;
  
  return <UserCard user={user} />;
}

這兩種方式的共同問題是:每個(gè)需要數(shù)據(jù)的組件都要重復(fù)編寫 loading 狀態(tài)的處理邏輯。當(dāng)應(yīng)用變得復(fù)雜時(shí),你會發(fā)現(xiàn)自己寫了無數(shù)個(gè) if (loading) return <Loading />。

3.2 Suspense 帶來的范式轉(zhuǎn)變

Suspense 改變了數(shù)據(jù)獲取的思維方式。它不是問「數(shù)據(jù)加載完了嗎」,而是問「數(shù)據(jù)準(zhǔn)備好了嗎」。這種轉(zhuǎn)變帶來了幾個(gè)重要的好處:

首先,關(guān)注點(diǎn)分離。組件只需要聲明它需要什么數(shù)據(jù),而不需要關(guān)心數(shù)據(jù)是如何獲取的。數(shù)據(jù)獲取的邏輯可以集中在另一個(gè)地方。

其次,統(tǒng)一的加載狀態(tài)。所有需要異步數(shù)據(jù)的組件都可以用同一個(gè) Suspense 邊界來管理加載狀態(tài),不需要在每個(gè)組件中重復(fù)編寫 loading 邏輯。

第三,更好的用戶體驗(yàn)。Suspense 允許你精確控制哪些部分顯示 loading,以及 loading 狀態(tài)的過渡動(dòng)畫。

3.3 使用 Suspense 的數(shù)據(jù)獲取方案

目前有幾種主流的 Suspense 數(shù)據(jù)獲取方案:

React Query / TanStack Query:

import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId)
  });

  if (isLoading) throw new Promise();
  if (error) throw error;
  
  return <UserCard user={data} />;
}

注意這里使用了 throw new Promise()。當(dāng) Suspense 檢測到子組件拋出了 Promise,它會自動(dòng)渲染 fallback。

SWR:

import useSWR from 'swr';

function UserProfile({ userId }) {
  const { data, error, isLoading } = useSWR(
    `/api/users/${userId}`,
    fetcher
  );

  if (isLoading) throw new Promise();
  if (error) throw error;
  
  return <UserCard user={data} />;
}

自定義 Suspense 數(shù)據(jù)源:

你也可以創(chuàng)建自己的 Suspense 數(shù)據(jù)獲取方案:

// createResource.js
function createResource(promise) {
  let status = 'pending';
  let result;

  const suspender = promise.then(
    data => {
      status = 'success';
      result = data;
    },
    error => {
      status = 'error';
      result = error;
    }
  );

  return {
    read() {
      if (status === 'pending') throw suspender;
      if (status === 'error') throw result;
      return result;
    }
  };
}

// 使用
const userResource = createResource(fetchUser(userId));

function UserProfile() {
  const user = userResource.read();
  return <UserCard user={user} />;
}

四、Suspense 與錯(cuò)誤邊界

Suspense 只能處理「組件掛起」的情況,不能直接處理渲染錯(cuò)誤。對于渲染錯(cuò)誤(如組件內(nèi)部拋出異常),你需要使用錯(cuò)誤邊界(Error Boundary)。

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    logError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <ErrorPage error={this.state.error} />;
    }
    return this.props.children;
  }
}

// 結(jié)合使用
function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<Loading />}>
        <MainContent />
      </Suspense>
    </ErrorBoundary>
  );
}

這種組合方式可以同時(shí)處理加載狀態(tài)和渲染錯(cuò)誤,提供完整的錯(cuò)誤處理方案。

五、Suspense 在 React 18 的演進(jìn)

React 18 對 Suspense 進(jìn)行了進(jìn)一步的增強(qiáng),特別是在并發(fā)渲染方面的支持。

5.1 過渡效果與 Suspense

React 18 引入了 startTransition API,它與 Suspense 配合使用可以實(shí)現(xiàn)更精細(xì)的渲染控制:

import { startTransition, Suspense } from 'react';

function SearchResults({ query }) {
  return (
    <Suspense fallback={<Loading />}>
      <Results query={query} />
    </Suspense>
  );
}

function App() {
  const [query, setQuery] = useState('');
  
  function handleChange(e) {
    // 緊急更新:輸入框立即響應(yīng)
    setQuery(e.target.value);
    
    // 非緊急更新:搜索結(jié)果可以等待
    startTransition(() => {
      // 這個(gè)更新會被標(biāo)記為「可中斷」
      // 如果用戶繼續(xù)輸入,當(dāng)前的搜索會被取消
      setSearchQuery(e.target.value);
    });
  }
  
  return (
    <>
      <input onChange={handleChange} />
      <SearchResults query={searchQuery} />
    </>
  );
}

5.2 新的 useDeferredValue

useDeferredValue 也可以與 Suspense 配合使用:

import { useDeferredValue, Suspense } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  return (
    <Suspense fallback={<Loading />}>
      <ResultsList query={deferredQuery} />
    </Suspense>
  );
}

六、Suspense 的性能優(yōu)化

6.1 預(yù)加載數(shù)據(jù)

你可以在用戶交互發(fā)生之前就開始預(yù)加載數(shù)據(jù):

import { preload } from 'react-dom';

function ProductCard({ product }) {
  const handleMouseEnter = () => {
    // 用戶懸停時(shí)開始預(yù)加載詳情數(shù)據(jù)
    preload(`/api/products/${product.id}`, fetch);
  };

  return (
    <div onMouseEnter={handleMouseEnter}>
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
    </div>
  );
}

6.2 合理劃分 Suspense 邊界

Suspense 邊界的劃分對性能有重要影響。邊界太粗會導(dǎo)致不必要的 loading,邊界太細(xì)會增加代碼復(fù)雜度。

// 不好的例子:整個(gè)頁面一個(gè) Suspense
function Page() {
  return (
    <Suspense fallback={<PageLoading />}>
      <Header />
      <Sidebar />
      <MainContent />
      <Footer />
    </Suspense>
  );
}

// 好的例子:按需劃分邊界
function Page() {
  return (
    <>
      <Header />
      <div className="content">
        <Suspense fallback={<SidebarSkeleton />}>
          <Sidebar />
        </Suspense>
        <Suspense fallback={<MainSkeleton />}>
          <MainContent />
        </Suspense>
      </div>
      <Footer />
    </>
  );
}

6.3 避免 Suspense 閃爍

當(dāng)數(shù)據(jù)加載非??鞎r(shí),頻繁顯示和隱藏 loading UI 會造成視覺閃爍。使用 maxDuration 可以緩解這個(gè)問題:

<Suspense fallback={<Loading />} maxDuration={300}>
  <Content />
</Suspense>

如果數(shù)據(jù)在 300ms 內(nèi)準(zhǔn)備好,React 會直接渲染內(nèi)容而不顯示 loading。

七、Suspense 的限制與注意事項(xiàng)

Suspense 雖然強(qiáng)大,但也有一些限制需要注意。

首先,Suspense 只能捕獲 Promise 拋出。如果你的組件拋出其他類型的錯(cuò)誤,Suspense 不會捕獲它。確保你的數(shù)據(jù)獲取邏輯只拋出 Promise 來觸發(fā) Suspense。

其次,服務(wù)端渲染(SSR)支持有限。雖然 React 18 的流式 SSR 支持 Suspense,但在某些場景下 Suspense 的行為可能與客戶端不同。

第三,不要在條件渲染中使用 Suspense。Suspense 組件必須在渲染時(shí)存在才能工作:

// 錯(cuò)誤:條件渲染 Suspense
{showContent && (
  <Suspense fallback={<Loading />}>
    <Content />
  </Suspense>
)}

// 正確:Suspense 始終存在,內(nèi)容條件渲染
<Suspense fallback={<Loading />}>
  {showContent ? <Content /> : null}
</Suspense>

第四,注意 Suspense 的嵌套層級。過深的嵌套會增加 React 的協(xié)調(diào)開銷,影響性能。

八、實(shí)戰(zhàn):構(gòu)建一個(gè) Suspense 驅(qū)動(dòng)的應(yīng)用

下面是一個(gè)完整的示例,展示如何構(gòu)建一個(gè) Suspense 驅(qū)動(dòng)的應(yīng)用:

// api.js - 數(shù)據(jù)獲取層
async function fetchUser(userId) {
  const res = await fetch(`/api/users/${userId}`);
  if (!res.ok) throw new Error('Failed to fetch user');
  return res.json();
}

async function fetchUserPosts(userId) {
  const res = await fetch(`/api/users/${userId}/posts`);
  if (!res.ok) throw new Error('Failed to fetch posts');
  return res.json();
}

async function fetchUserComments(userId) {
  const res = await fetch(`/api/users/${userId}/comments`);
  if (!res.ok) throw new Error('Failed to fetch comments');
  return res.json();
}

// resources.js - 資源創(chuàng)建
function createResource(promise) {
  let status = 'pending';
  let result;
  let error;

  const promiseThen = promise.then(
    value => {
      status = 'success';
      result = value;
    },
    err => {
      status = 'error';
      error = err;
    }
  );

  return {
    read() {
      if (status === 'pending') throw promiseThen;
      if (status === 'error') throw error;
      return result;
    }
  };
}

// components.js - 組件層
function UserProfile({ userId }) {
  const userResource = createResource(fetchUser(userId));
  const user = userResource.read();
  
  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

function UserPosts({ userId }) {
  const postsResource = createResource(fetchUserPosts(userId));
  const posts = postsResource.read();
  
  return (
    <div className="user-posts">
      <h3>Posts</h3>
      {posts.map(post => (
        <article key={post.id}>
          <h4>{post.title}</h4>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

function UserComments({ userId }) {
  const commentsResource = createResource(fetchUserComments(userId));
  const comments = commentsResource.read();
  
  return (
    <div className="user-comments">
      <h3>Comments</h3>
      {comments.map(comment => (
        <div key={comment.id} className="comment">
          <p>{comment.text}</p>
          <span>{comment.date}</span>
        </div>
      ))}
    </div>
  );
}

// App.js - 主應(yīng)用
function App() {
  const [userId, setUserId] = useState(1);

  return (
    <div className="app">
      <header>
        <h1>User Dashboard</h1>
        <button onClick={() => setUserId(id => id + 1)}>
          Next User
        </button>
      </header>

      <main>
        <ErrorBoundary>
          <Suspense fallback={<UserProfileSkeleton />}>
            <UserProfile userId={userId} />
          </Suspense>
          
          <div className="grid">
            <Suspense fallback={<PostsSkeleton />}>
              <UserPosts userId={userId} />
            </Suspense>
            
            <Suspense fallback={<CommentsSkeleton />}>
              <UserComments userId={userId} />
            </Suspense>
          </div>
        </ErrorBoundary>
      </main>
    </div>
  );
}

這個(gè)示例展示了 Suspense 的幾個(gè)關(guān)鍵特性:獨(dú)立的 Suspense 邊界、錯(cuò)誤邊界的配合使用,以及資源預(yù)加載的模式。

總結(jié)

React Suspense 為異步數(shù)據(jù)獲取提供了一種全新的編程范式。它讓組件能夠聲明式地表達(dá)「我需要這些數(shù)據(jù)」,而 React 負(fù)責(zé)處理加載狀態(tài)的管理和 UI 的切換。通過合理劃分 Suspense 邊界、配合錯(cuò)誤邊界使用、以及利用 React 18 的并發(fā)特性,你可以構(gòu)建出用戶體驗(yàn)優(yōu)秀的應(yīng)用。

到此這篇關(guān)于React Suspense 異步渲染完全指南的文章就介紹到這了,更多相關(guān)React Suspense 異步渲染內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • React?組件中?State?的定義、使用及正確使用方式

    React?組件中?State?的定義、使用及正確使用方式

    本文詳細(xì)介紹了React組件中的state概念,包括其定義、使用方式以及如何正確更新state,通過ES6和ES7類組件的示例,展示了如何在React中定義和使用state,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • react-router-dom之異步加載路由方式

    react-router-dom之異步加載路由方式

    這篇文章主要介紹了react-router-dom之異步加載路由方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • react中(含hooks)同步獲取state值的方式

    react中(含hooks)同步獲取state值的方式

    這篇文章主要介紹了react(含hooks)中同步獲取state值的方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • react+ant design實(shí)現(xiàn)Table的增、刪、改的示例代碼

    react+ant design實(shí)現(xiàn)Table的增、刪、改的示例代碼

    這篇文章主要介紹了react+ant design實(shí)現(xiàn)Table的增、刪、改的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-12-12
  • 詳解react-router 4.0 下服務(wù)器如何配合BrowserRouter

    詳解react-router 4.0 下服務(wù)器如何配合BrowserRouter

    這篇文章主要介紹了詳解react-router 4.0 下服務(wù)器如何配合BrowserRouter,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • React如何實(shí)現(xiàn)Vue的watch監(jiān)聽屬性

    React如何實(shí)現(xiàn)Vue的watch監(jiān)聽屬性

    在 Vue 中可以簡單地使用 watch 來監(jiān)聽數(shù)據(jù)的變化,還能獲取到改變前的舊值,而在 React 中是沒有 watch 的,今天通過本文給大家講解React實(shí)現(xiàn)Vue的watch監(jiān)聽屬性的方法,需要的朋友可以參考下
    2023-06-06
  • React Fiber 架構(gòu)解決頁面卡頓問題的全過程

    React Fiber 架構(gòu)解決頁面卡頓問題的全過程

    本文從問題與目標(biāo)、核心數(shù)據(jù)結(jié)構(gòu)、調(diào)度與中斷、渲染階段與提交階段、優(yōu)先級與 lanes、并發(fā)特性到常見誤區(qū)與優(yōu)化建議,全景式拆解 React Fiber,為何它能夠顯著降低交互卡頓并提升可響應(yīng)性,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • react-native ListView下拉刷新上拉加載實(shí)現(xiàn)代碼

    react-native ListView下拉刷新上拉加載實(shí)現(xiàn)代碼

    本篇文章主要介紹了react-native ListView下拉刷新上拉加載實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • React19 Diff 算法的具體實(shí)現(xiàn)

    React19 Diff 算法的具體實(shí)現(xiàn)

    本文主要介紹了React19 Diff 算法的具體實(shí)現(xiàn),該算法用于高效地更新虛擬DOM,它通過優(yōu)化傳統(tǒng)樹diff算法,將復(fù)雜度從O(n^3)降低到O(n),感興趣的可以了解一下
    2026-05-05
  • 在React中使用SVG的幾種方式

    在React中使用SVG的幾種方式

    在React中,SVG(Scalable?Vector?Graphics)的使用非常普遍,因?yàn)樗鼈兲峁┝丝缮炜s的矢量圖形,這對于現(xiàn)代Web應(yīng)用來說是非常重要的,以下是幾種常見的在React中使用SVG的方法,每種方法都有其特定的用例和最佳實(shí)踐,需要的朋友可以參考下
    2024-11-11

最新評論

淄博市| 东兴市| 华容县| 新兴县| 辽宁省| 金阳县| 富源县| 静海县| 白沙| 沅江市| 通山县| 苗栗县| 绍兴县| 通辽市| 深州市| 桐城市| 灯塔市| 九龙城区| 尼勒克县| 桃园市| 陇南市| 新和县| 格尔木市| 丘北县| 芜湖县| 鲜城| 和林格尔县| 灌南县| 临洮县| 宿州市| 德清县| 榆林市| 方正县| 扎囊县| 武定县| 保德县| 平原县| 中阳县| 崇阳县| 墨脱县| 铜鼓县|