Transition 核心概念:Transition是一種標記非緊急任務更新的機制,它允許React在用戶交互(如輸入)期間保持界面的響應,同時準備后臺更新
主要特點:
- 區分優先級:可以將更新分為緊急非緊急任務
- 可中斷渲染:Transition更新可以被更緊急的交互打斷
- 自動降級:在不支持并發環境中自動回退到同步渲染
場景:
在Input中輸入搜索內容,過濾列表,由于數據量比較大10萬條數據導致頁面卡死
那如何處理此瓶頸:10萬條數據分頁還是利用虛擬滾動實現假性分頁。
如果把同步更新任務變成異步更新任務是不是就可以解決問題。Transition就可以處理多個并發任務
Input表單的并發任務分別為:
- 更新Input的內容,同時會觸發更新任務(高優先級的任務)
- Input內容改變,過濾列表,重新渲染也是一個任務(低優先級任務)
這里的任務可以分為緊急優先級任務和非緊急任務
緊急任務就是當用戶改變Input框的內容時候要立馬能夠看到更新后的內容,不然就會有卡頓、延遲。會有一種極差的視覺體驗
非緊急任務是當input輸入內容后,過濾列表并重新渲染列表,這個過程如果有延遲,用戶也是可以接受的
什么是緊急任務:直接影響用戶即時交互的界面更新,在本例中,輸入框value的更新、輸入框的光標位置、焦點狀態等
什么是非緊急任務:可以稍后處理的計算密集型或網絡請求
為什么緊急:每次用戶操作后需要立即看到輸入后的反饋,否則頓感卡頓,如果延遲處理,會導致輸入內容與顯示不一致,糟糕的用戶體驗
為什么非緊急:用戶能夠容忍短暫的結果延遲,如果與輸入框競爭資源,反而會導致輸入卡頓
Transition基本用法:
import { startTransition } from 'react';// 在事件處理中
function handleInputChange(e) {const value = e.target.value;// 緊急更新:立即更新輸入框setInputValue(value);// 非緊急更新:標記為TransitionstartTransition(() => {setSearchQuery(value); // 可能觸發大量計算的更新});
}
使用 useTransition Hook
import { useTransition } from 'react';function SearchBox() {const [isPending, startTransition] = useTransition();const handleChange = (e) => {const value = e.target.value;setInputValue(value);startTransition(() => {setSearchQuery(value);});};return (<div><input onChange={handleChange} />{isPending && <Spinner />} {/* 顯示過渡狀態 */}</div>);
}
上述案例完整代碼:
import { useState, useTransition } from 'react';function SearchComponent() {const [inputValue, setInputValue] = useState('');const [searchResults, setSearchResults] = useState([]);const [isPending, startTransition] = useTransition();// 實際項目中實現的搜索函數async function performHeavySearch(keyword) {const response = await fetch(`/api/search?q=${keyword}`);const data = await response.json();return data.items; // 根據實際API結構調整}const handleChange = async (e) => {const value = e.target.value;setInputValue(value);startTransition(async () => {const results = await performHeavySearch(value);setSearchResults(results);});};return (<div><input value={inputValue} onChange={handleChange} />{isPending ? (<div>Searching...</div>) : (<ul>{searchResults.map(item => (<li key={item.id}>{item.name}</li>))}</ul>)}</div>);
}
典型案例:
- 搜索輸入:輸入時保持輸入框響應,搜索結果稍后顯示
- 標簽頁切換:點擊切換標簽時立即顯示激活狀態,內容稍后加載
- 大數據渲染:優先渲染可見部分,其他內容漸進加載
與Suspense的結合使用:Transition 可以與 Suspense 完美配合,實現流暢的異步加載體驗:
import { Suspense, useTransition } from 'react';function App() {const [resource, setResource] = useState(initialResource);const [isPending, startTransition] = useTransition();function fetchNewData() {startTransition(() => {setResource(fetchData()); // 返回一個Suspense兼容的資源});}return (<div><button onClick={fetchNewData}disabled={isPending}>{isPending ? 'Loading...' : 'Load Data'}</button><Suspense fallback={<Spinner />}> {/* Spinner 顯示過渡狀態 */}<DataDisplay resource={resource} /></Suspense></div>);
}
React中Suspense和核心功能:
1、作用:
- 用在子組件(懶加載或異步數據請求)完成加載前顯示一個后備方案(fallback UI),例如加載動畫或者占位符
- 支持代碼分割(通過React.lazy)和異步數據加載(需支持Suspense的庫,如react query或relay)
2、關鍵特性:
- 代碼分割:與react.lazy結合,實現組件按需加載
- 數據加載:需依賴Suspense庫,原生react不支持異步數據Suspense
- 嵌套使用:允許逐步加載內容,優化用戶體驗
3、示例:
<Suspense fallback={<Loading />}><LazyComponent /> // 通過 React.lazy 加載
</Suspense>
與vue中的Suspense
1、作用
- 處理異步組件或異步setup函數加載狀態,顯示后備內容
- 支持任意異步邏輯(如數據請求或動態導入組件),不限于組件級懶加載
2、關鍵特性
- 插槽設計:通過#default和#fallback插槽管理內容
- 事件監聽:提供pending、resolve等事件、便于控制加載狀態
- 嵌套支持:子組件的異步依賴會觸發父級Suspense的fallback
3、示例
<Suspense><template #default><AsyncComponent /> // 異步組件或含 async setup 的組件</template><template #fallback><Loading /></template>
</Suspense>
主要區別:
特性 | React Suspense | Vue Suspense |
---|---|---|
支持范圍 | 主要針對組件懶加載、異步數據請求、數據需要第三方支持 | 支持任意異步邏輯(組件、數據等) |
API 設計 | 通過fallback prop定義占位內容 | 使用插槽(#default 和 #fallback |
嵌套行為 | 內部Suspense優先處理 | 父級的Suspense等子異步依賴完成再處理 |
事件監聽 | 無內置事件 | 通過pending、resolve、fallback等事件 |
vue3中Suspense中事件用法
<template><Suspense @pending="onPending"@resolve="onResolve"@fallback="onFallback"><template #default><AsyncComponent /></template><template #fallback><div>Loading...</div></template></Suspense>
</template><script setup>
import { defineAsyncComponent } from 'vue';const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue')
);function onPending() {console.log('開始異步加載');// 可以在這里顯示全局加載狀態// 發送分析事件analytics.track('DashboardLoadStart');
}function onResolve() {console.log('異步加載完成');// 可以在這里隱藏全局加載狀態// 發送性能數據analytics.track('DashboardLoaded', { duration: loadTime });
}function onFallback() {console.log('Fallback內容被展示');// 可以記錄fallback顯示時間等
}
</script>
備注:這里面可以用作性能監控,當開始的時候發送事件分析,當結束時候獲得性能數據
代碼分割詳解:
代碼分割是前端性能優化中的重要技術。
代碼分割是將整個應用分割成多個小塊,然后按需加載,而不是一次性加載所有代碼,這可以:
- 顯著減少初始加載時間
- 降低首屏資源體積
- 通過應用交互響應速度
傳統代碼分割問題:
// 傳統動態導入方式
import("./MyComponent.js").then(module => {// 組件加載完成后才能使用
});
- 需要手動處理加載狀態
- 容易導致布局跳動或空白
- 代碼組織不夠直觀
Suspense如何分割代碼
React:
const MyComponent = React.lazy(() => import('./MyComponent'));function App() {return (<Suspense fallback={<div>Loading...</div>}><MyComponent /> {/* 被代碼分割的組件 */}</Suspense>);
}
Vue3:
<script setup>
const AsyncComp = defineAsyncComponent(() => import('./MyComponent.vue')
)
</script><template><Suspense><template #default><AsyncComp /></template><template #fallback><div>Loading...</div></template></Suspense>
</template>
- 動態導入組件
- 導入過程中,Suspense顯示fallback內容
- 加載完成后,替換為實際組件
技術實現細節:
Webpack的代碼分割:當使用import()語法時,打包工具會自動:
- 將目標分割成一個獨立的chunk文件
- 生成運行時候加載邏輯
- 在需要時通過 JSONP動態獲取
Suspense的協調機制:
- React/Vue會自動追蹤異步組件加載狀態
- 在模塊加載期間暫停渲染
- 加載完成后重新觸發渲染
為什么需要Suspense
無Suspense | 有Suspense |
---|---|
需要手動維護loading | 統一處理loading |
多個異步加載時狀態復雜 | 支持嵌套異步依賴 |
錯誤處理困難 | 與錯誤邊界(Error Boundaries)天然集成 |
性能優化技巧:
預加載策略:
// 鼠標懸停時預加載
function onLinkHover() {import('./ComponentToPrefetch');
}
命名chunks:
const Component = lazy(() => import(/* webpackChunkName: "specific-name" */ './Component'
));
Suspense嵌套:
<Suspense fallback={<AppLoader />}><Layout><Suspense fallback={<SidebarLoader />}><Sidebar /></Suspense></Layout>
</Suspense>