import()異步加載模塊
在webpack中, 調用import()函數可以將依賴模塊進行切割, 打包為非入口點文件, 這是通過Promise+ajax完成的. 請求路徑是相對路徑, 對於單頁應用來說沒有問題.
非入口點文件的命名由webpack.config.output.chunkFilename(可以定義路徑, 使用[name]和[id]變量)以及Magic Comment(定義[name]變量)共同確定.
React.lazy()函數
React.lazy() 允許你定義一個動態加載的組件。這有助於縮減 bundle 的體積,並延遲加載在初次渲染時未用到的組件。
// 這個組件是動態加載的
const SomeComponent = React.lazy(() => import('./SomeComponent'));
渲染 lazy 組件依賴該組件渲染樹上層的 <React.Suspense> 組件。這是指定加載指示器(loading indicator)的方式。
使用 React.lazy 的動態引入特性需要 JS 環境支持 Promise。在 IE11 及以下版本的瀏覽器中需要通過引入 polyfill 來使用該特性。
! 不支持服務端渲染。
例子 -- lazy加載組件
import { Component, lazy, Suspense } from 'react';
import css from './style.css';
/** 異步組件使用lazy()函數加載, 傳遞一個使用import()函數的Promise異步方法, 該方法最終返回import()函數的結果 */
const AsyncComponent = lazy(() => {
return new Promise((resolve, reject) => {
import('./AsyncComponent').then(AsyncComponent => {
console.log('加載完畢, 延遲傳送');
setTimeout(() => {
console.log('傳送');
resolve(AsyncComponent);
}, 5000);
});
});
});
export default (
<div id={css.app}>
<Suspense fallback={<h1>加載中</h1>}>
<AsyncComponent></AsyncComponent>
</Suspense>
</div>
);
Suspense組件
React關注DOM和事件, 數據更新更是重要, 因此, 我們來看Suspense組件如何在異步操作與UI更新之間建起橋梁.
代碼分割: https://zh-hans.reactjs.org/docs/code-splitting.html#reactlazy
Suspense: https://zh-hans.reactjs.org/docs/concurrent-mode-suspense.html
Suspense還用於異步數據的獲取:
官方示例: https://codesandbox.io/s/frosty-hermann-bztrp?file=/src/fakeApi.js
拋出Promise: 使用throw關鍵字陷入React內核
在執行異步操作的過程中, 我們只需要在Promise未完成狀態時將該Promise拋出到React核心即可:
function fetchName() {
console.log('嘗試聯網獲取用戶名...');
throw new Promise(()=> {
console.log('拋出一個永久pending狀態的Promise');
});
}
function FunctionComponent(props) {
console.log('嘗試獲取用戶名並渲染UI...');
return <h2>用戶名: {fetchName()}</h2>
}
export default (
<div id={css.app}>
<Suspense fallback={<h1>正在聯網獲取用戶名...</h1>}>
<FunctionComponent/>
</Suspense>
</div>
);
Promise的拒絕狀態會導致組件立即重新渲染, 並可能不斷重復:
function fetchName() {
console.log('嘗試聯網獲取用戶名...');
throw new Promise((_, reject)=> {
console.log('拋出一個拒絕狀態的Promise');
reject();
});
}
拋出其它非Promise異常會被React重新拋出, 導致頁面報錯.
如果一個拋出的Promise結束了成為success狀態, 那么它之后應該返回相應的結果, 而不是再次拋出Promise, 因為success狀態的Promise(這里可能是全部的Promise結束后再調用)會導致方法組件再次被調用以渲染元素.
import { Component, lazy, Suspense, createElement, useRef } from 'react';
import css from './style.css';
// 單例
let fetchNameByInternet = () => new Promise(resolve => {
console.log('網絡請求開始了, 將於4秒后完成');
setTimeout(() => resolve('develon'), 4000);
});
let resolved = false; // 標志網絡請求是否已完成
let name = "Don't get the Name"; // 存儲從網絡獲取的用戶名
function fetchName() {
console.log('嘗試聯網獲取用戶名...');
if (resolved) {
console.log(`獲取到數據: "${name}" !`);
return name;
}
throw fetchNameByInternet().then(network_name => {
resolved = true;
name = network_name;
});
}
function FunctionComponent(props) {
console.log('方法組件被調用, 嘗試獲取用戶名並渲染UI...');
let name = fetchName(); // 此處會拋出異常, 不可進行捕獲, 從而就像CPU中斷指令一樣陷入React內核
console.log('方法組件繼續執行, 開始渲染元素'); // 整個方法結束, 只有當Promise成功之后, 才會再次調用該方法組件, 所以說這些異步操作是有順序的
return <h2>用戶名: {name}</h2>;
}
export default (
<div id={css.app}>
<Suspense fallback={<h1>正在聯網獲取用戶名...</h1>}>
<FunctionComponent/>
</Suspense>
</div>
);