忙于业务,已经好久没有关注 react 的文档了,貌似 16.6.x 的文档和 16.5.x 变动还是挺大的, 尤其是在高级指南。

一、动态 import

Code-Splitting 部分,提出拆分组件的最佳方式(best way) 是使用动态的 import 方式。

比如下面两种使用方式的对比:

之前:

import { add } from './math';

console.log(add(16, 26));

之后:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

可以发现动态 import 提供了 Promise 规范的 API,比如 .then(),关于 ES 动态 import,可以查看下面链接:

同样,下面这篇文章上也可以参考:

目前动态 import 仍旧是 ECMAScript 的提案,并没有纳入规范,不过既然 react 能够大力的推进,应该下个标准会被写入。可以查看 TC39-https://github.com/tc39/proposal-dynamic-import

动态 import 主要用延迟请求,对于组件我觉得没什么太大的用处,但是对于延迟加载方法或者bundle非常有用,比如下面的代码:

可以发现,当触发点击事件的时候,才会去引入需要的方法或者是对象,并且由于 Promise API 的特性,可以使用 Promise.all Promise.race 这种 API,进行并行加载,然后在 then() 回调中调用方法,非常方便

class App extends Component {
  clickHandle = (e) => {
    Promise.all([
      import('./mods/Lazy2')
    ]).then(([a]) => {
      console.log(a);
        a(e);
    });
  }
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <button onClick={this.clickHandle}>click</button>
        </header>
      </div>
    );
  }
}

webpack 已经支持动态 import,在 create-react-app 和 next.js 中都已经可以使用。如果是自己的 webpack 脚手架,需要在 webpack 中进行配置,具体的可以参考下面的方式https://webpack.js.org/guides/code-splitting/,最终配置完的样式类似于:https://gist.github.com/gaearon/ca6e803f5c604d37468b0091d9959269(这个链接是react文档给出的额,但是我的网络无法访问)

同样的,如果使用 babel,需要使用babel-plugin-syntax-dynamic-import这个组件来保证Babel不对动态导入进行转换。

二、React.lazy() 和 Suspense

1、React.lazy()

动态 import 主要应用场景是延迟加载方法,对于组件来说,并不是很适用,但是 React.lazy 对于组件的加载则是有比较大的帮助。

目前明确指出,React.lazy 和 suspense 并不适用于服务端渲染

先看一下前后的区别,

之前代码:

import OtherComponent from './OtherComponent';

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

之后:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

关键点是 const OtherComponent = React.lazy(() => import('./OtherComponent')); 这个语句,摒弃了之前的 import X from 'x' 的静态引入方式。

同样的,这个变动会使得在组件渲染的时候,再去加载包含引入路径的组件。

React.lazy(()=>{}) 这个方法的回调中其实就是包含了一个动态 import, 以下面方式举例:

const Lazy2 = React.lazy(() =>import('./mods/Lazy2').then((e) => {
  console.log(e);
}));

箭头句柄后面就是一个动态 import 的使用, 打印出来的 e,也就是引入的内容,而前面引入的是个组件,因此会打印出如下信息:

微信截图_20181102004623.png

2、Suspense

要使用 Suspense,需要从 react 中 import:

import React, { Component, Suspense } from 'react';

既然是延迟加载,就会有一个加载过程,之前在渲染的时候,我们基本都是自顶一个一个 <Loading> 组件,然后通过变量控制进行操作,如果加载完成,则取消掉 <Loading> 组件。

如果直接使用 React.lazy,会报错误:需要一个 placeholder ui

3.png

既然是延迟加载,就一定会有一个loading的过程,而 Suspense 正是完成这个过程。

111111111111111.gif

如同上面的效果会有一个动态的过程,代码如下:

render() {
  return (
    <div className="App">
      <header className="App-header">
      <Suspense fallback={<div>Loading...</div>}>
        {this.renderList()}
      </Suspense>
      </header>
    </div>
  );
}

Suspense 使用的时候,fallback 一定是存在且有内容的, 否则会报错。

针对网络请求的 loading,我并没觉的这种 fallback 有什么帮助,因为他是组件加载的 loading,如果组件加载完了,那么再去 fallback 就没意义,也没效果了。

三、Error boundaries

上面 Suspense 是对 loading 的一个打底,而错误边界可以在任何一个组件中进行错误的捕获。

这里只对错误边界进行一个简要的使用,具体的文档见下:

这里的错误边界用在这个位置是为了当组件懒加载失败的时候,进行错误的捕获和保护:

1、创建错误边界组件

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  // 从error中接收错误并设置 state
  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

2、在组件中使用错误边界

将创建的错误边界挂到组件中,不适用 React.lazy,因为已经没有更上一层次的错误组件了,万一错误边界组件也懒加载出错,会导致无法捕获。

错误边界组件中,通过 componentDidCatch 捕获错误,可以设置信息或发错误日志。

错误边界的使用示例可以参考下面的示例:

按照上面的例子,在使用的时候,组件中报错,触发错误边界:

Lazy2.jsx:

render() {
    throw new Error('I crashed!');
    return (
      <div>
        <p>{this.state.title}</p>
      </div>
    );
  }

App.jsx:

 <Error>
    {this.renderList()}
 </Error>

Error.jsx:

import React, {Component} from 'react';

class Error extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}

export default Error;

最终结果:

1541094096(1).jpg