React 入門(7): 動態加載組件


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>
);

END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM