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

posted @ 2020-09-21 16:00  develon  阅读(9282)  评论(0编辑  收藏  举报