代码分片:React.lazy和ErrorBoundary的使用

第一个项目中优化性能的时候作了懒加载和代码分隔,有一点小的收获。语言很浅显直白,望各位大神包涵。javascript

1. 为何要使用react.lazy?

React.lazy属于代码分片(code spliting),当时就很不理解所谓的懒加载和代码分片到底有什么区别。css

收获:

相同点:其实懒加载和代码分片都是按需加载,都能优化页面的性能html

懒加载:懒加载是在用户交互层面来按需加载,实现性能优化的。原理是经过检测某元素或某组件是否在可视范围内从而决定是否加载渲染该组件。(用scroll监听事件配合一些计算IntersectionObserver API来判断该组件或该元素是否可见)java

代码分片:代码分片更为直接,是在js代码加载层面来实现按需加载的。咱们一般把js打到一个包里边,而React.lazy的做用就是把这部分能够置后加载的内容从包里拆分出来,打成另外一个包,须要的时候,再去加载这部分js。react

个人使用场景:

商品的详情页面,先不加载非首屏的js文件,等到须要的时候再加载,从而优化首屏的加载速度。webpack

效果

代码分片虽然只是很小的一个改动,可是自从7月5号开始上线了这个优化之后,效果仍是很明显的。git

性能提高

2. 如何使用React.lazy和ErrorBoundary

第1步:使用React.lazy引入须要置后加载的部分。 React.lazy接受一个函数,这个函数须要动态import一个React组件。这个函数会返回一个Promise,该Promise resolve的就是咱们但愿稍后加载的React组件。github

第2步:必须给置后加载的部分包裹一层Suspense,不然会报错。Suspense有一个fallback属性,接受一个React Component。至关于一个placeholder。在没加载出来这部份内容时临时占位的UI。能够是loading组件或者一个<Fragment/>web

// 第0步:引入React库中的lazy和Suspense
import React, { Component, lazy, Suspense, Fragment } from 'react';
// 页头
import Header from 'containers/product-store-header';
// 首屏
import FirstScreen from 'containers/first-screen';

import './index.scss';

// 第1步:用React.lazy导入须要置后加载的部分
const RestPart = lazy(() => import('containers/rest-part'));

class App extends Component {
    render() {
        return (
            <div className="detail-wrap"> <Header /> <FirstScreen /> <!--第2步:包裹上Suspense,fallback设为空即<Fragment />--> <Suspense fallback={<Fragment />}> <RestPart /> </Suspense> </div>
        );
    }
}
export default App;

复制代码

第3步:给Suspense外再包裹一层错误边界ErrorBoundary。为何要这层错误边界呢?由于若是这部分稍后加载的js出了问题(网络缘由),没能成功加载,会致使整个页面崩溃。错误边界的做用有点像catch,能够捕获子组件的错误。即使稍后加载的这部份内容有问题,也会显示ErrorBoundary里设定的降级的UI而不会致使整个页面崩溃。性能优化

3.1 写一个ErrorBoundary组件。

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';

class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }

    static propTypes = {
        children: PropTypes.element
    }

    static getDerivedStateFromError(error) {
        // 更新 state 使下一次渲染可以显示降级后的 UI
        return { hasError: true };
    }

    componentDidCatch(error, errorInfo) {
        // 将错误日志上报给服务器
        // some code here...
    }

    render() {
        const { hasError } = this.state;
        const { children } = this.props;
        
        // 这里个人自定义降级后UI为空即<Fragment />
        // 你能够自定义降级后的 UI 并渲染
        return hasError ? <Fragment /> : children; } } export default ErrorBoundary; 复制代码

3.2 包裹上错误边界

// 第0步:引入React库中的lazy和Suspense
import React, { Component, lazy, Suspense, Fragment } from 'react';
// 页头
import Header from 'containers/product-store-header';
// 首屏
import FirstScreen from 'containers/first-screen';

import './index.scss';

// 第1步:用React.lazy导入须要置后加载的部分
const RestPart = lazy(() => import('containers/rest-part'));

class App extends Component {
    render() {
        return (
            <div className="detail-wrap"> <Header /> <FirstScreen /> <!--第3步:包裹一层错误边界--> <ErrorBoundary> <!--第2步:包裹上Suspense,fallback设为空即<Fragment />--> <Suspense fallback={<Fragment />}> <!--第1步:React.lazy引入的须要稍后加载的部分 --> <RestPart /> </Suspense> </ErrorBoundary> </div>
        );
    }
}
export default App;

复制代码

至此,React.lazy和ErrorBoundary的一个简单的使用就完成了。

3.踩过的坑

3.1 webpackChunkName颇有用

由于要作A/Btest,因此我打包了两份代码,因为两份代码配置不一样,有两个webpack的实例。因而在本地测试代码分片的时候,上边的代码就出问题了。缘由是本地打包的时候,js文件名是没有哈希值后缀的,致使AB两份代码对于非首屏的js代码打包后名字相同,加载的时候就报错了。解决方法十分简单,给每份用react.lazy引入的组件添加一个不一样的webpackChunkName就能够了。

// 会报错的写法
const RestPart = lazy(() => import('containers/rest-part'));

// 改进后的写法 A代码里
const RestPart = lazy(() => import(/* webpackChunkName: "rest" */'containers/rest-part'));

//改进后的写法 B代码里
const RestPart = lazy(() => import(/* webpackChunkName: "rest-b" */'containers/rest-part'));
复制代码

3.2 若是利用代码分片异步加载React Component

若是是由于网络缘由致使非首屏的js加载出错,但咱们但愿能显示一个按钮,点击一下,能够从新加载一次出错的资源。该怎么办呢?

  • 最low的写法: 固然是location.reload()刷新整个页面了。
  • 推荐的写法: 因为已经出错的React Component部分返回的是一个Promise,已是reject状态了,若是放在ErrorBoundary外部import,ErrorBoundary接收到的永远都是错误的状态。可是若是把动态引入和ErrorBoundary里边,则会从新import一次。此时,咱们只需更新ErrorBoundary里hasError的状态便可。
import React, { Component, Fragment, lazy, Suspense } from 'react';
import PropTypes from 'prop-types';

class RestWithErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }

    static propTypes = {
        children: PropTypes.element
    }

    static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级后的 UI
        return { hasError: true };
    }

    componentDidCatch(error, errorInfo) {
         // 将错误日志上报给服务器
        // some code here...
    }

    reloadJs() {
        this.setState((state) => {
            return { hasError: state.false };
        });
    }

    render() {
        const { hasError } = this.state;
        const RestPart = lazy(() => import(/* webpackChunkName: "rest" */ 'containers/rest-part'));
        const reloadButton = (<button onClick={this.reloadJs.bind(this)}>Click to reload</button>);
        return hasError ?
            reloadButton :
            <Suspense fallback={<Fragment />}> <RestPart /> </Suspense>;
    }
}

export default RestWithErrorBoundary;

复制代码

4. 注意事项

  1. React.lazy 和 Suspense 技术还不支持服务端渲染。 若是你想要在使用服务端渲染的应用中使用,请参考 Loadable Components 这个库。
  2. 错误边界的工做方式相似于 JavaScript 的 catch {},不一样的地方在于错误边界只针对 React 组件。只有 class 组件才能够成为错误边界组件。且错误边界不能捕获自身的错误,只能捕获其子组件的错误。

5. 参考资料 --- React官网

1.代码分片/code spliting

2.错误边界/error boundary

相关文章
相关标签/搜索