React 16是React的一个重要版本于2017年9月正式发布。

这个版本带来了很多新特性和改进。比如全新的Fiber架构错误边界Portals渲染数组和字符串自定义DOM属性setState返回null等等。

这些新特性让React更强大更灵活性能也更好。

但是很多人对React 16的新特性理解还不够深入使用的时候,也经常会遇到各种配置问题。

今天想详细讲解一下React 16的新特性和配置方法从基础到高级。

希望通过这篇文章大家能更好地理解和使用React 16的新特性。

一、React 16概述

先简单介绍一下React 16。

React 16的代号是Fiber。因为这个版本最大的变化就是引入了全新的Fiber架构。

Fiber是React核心算法的重写。它让React的渲染过程变得可中断可恢复可优先级调度。

在React 15及之前React的渲染过程是同步的一旦开始就不能中断。如果组件树很大渲染时间长就会阻塞主线程导致页面卡顿掉帧。

而React 16的Fiber架构把渲染过程拆成了一个个小的任务单元可以中断和,恢复。React可以根据任务的优先级来调度渲染高优先级的任务先执行低优先级的任务可以延后。

这样就能保证用户交互的流畅性避免页面卡顿掉帧。

除了Fiber架构React 16还带来了很多新特性下面逐一讲解。

二、错误边界(Error Boundaries)

第一个新特性是错误边界。

在React 15及之前,如果一个组件在渲染过程中抛出了错误整个组件树都会崩溃白屏用户什么都看不到体验很差。

React 16引入了错误边界的概念。错误边界是一种特殊的组件能捕获它的子组件树中发生的JavaScript错误,并且渲染备用的UI而不是让整个组件树崩溃。

如何创建错误边界:

要创建一个错误边界只需要在组件中定义一个新的生命周期方法componentDidCatch。

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

  componentDidCatch(error, info) {
    // 捕获到错误
    this.setState({ hasError: true });
    // 可以上报错误日志
    console.log(error, info);
  }

  render() {
    if (this.state.hasError) {
      // 渲染备用UI
      return <h1>出了点问题请稍后再试</h1>;
    }
    return this.props.children;
  }
}

然后就可以像普通组件一样使用它:

<ErrorBoundary>
  <MyComponent />
</ErrorBoundary>

如果MyComponent在渲染过程中抛出了错误ErrorBoundary会捕获这个错误,并且渲染备用的UI"出了点问题请稍后再试"而不是让整个页面白屏。

注意事项:

  1. 错误边界只能捕获子组件的错误不能捕获自己的错误。
  2. 错误边界只能捕获渲染过程中的错误不能捕获事件处理器中的错误异步代码中的错误服务端渲染的错误。
  3. componentDidCatch有两个参数:error是,抛出的错误对象info是包含组件栈信息的对象。
  4. 错误边界可以嵌套使用不同的错误边界可以捕获不同层级的错误。

实际应用场景:

错误边界在实际项目中很有用。比如可以在页面的顶层放一个错误边界捕获整个页面的错误避免白屏。也可以在每个模块放一个错误边界某个模块出错不影响其他模块。

三、渲染数组和字符串

第二个新特性是渲染数组和字符串。

在React 15及之前,组件的render方法必须返回一个单一的React元素不能返回数组,或者字符串。如果要返回多个元素必须用一个父元素包裹起来,比如div。

这经常会导致很多不必要的嵌套div影响DOM结构也影响样式。

React 16允许组件的render方法返回数组和字符串。

返回数组:

render() {
  return [
    <li key="1">第一项</li>,
    <li key="2">第二项</li>,
    <li key="3">第三项</li>,
  ];
}

注意返回数组的时候,每个元素都需要有唯一的key属性和之前,数组渲染的要求一样。

返回字符串:

render() {
  return 'Hello World';
}

返回字符串的时候React会直接渲染文本节点不需要包裹任何元素。

实际应用场景:

这个特性在实际项目中很有用。比如列表组件可以直接返回多个li不需要再用ul包裹。比如一些纯文本组件可以直接返回字符串不需要用span包裹。

这样能减少不必要的DOM嵌套让DOM结构更简洁。

四、Portals

第三个新特性是Portals。

Portals提供了一种方式将子节点渲染到父组件DOM树之外的其他DOM节点中。

在React 15及之前,如果要把一个组件渲染到body或者其他DOM节点中需要用ReactDOM.unstable_renderSubtreeIntoContainer这个API不稳定也不推荐。

React 16正式引入了ReactDOM.createPortal这个API来实现这个功能。

如何使用Portals:

import ReactDOM from 'react-dom';

class Modal extends React.Component {
  render() {
    return ReactDOM.createPortal(
      this.props.children,
      document.body
    );
  }
}

上面的代码把Modal组件的子节点渲染到了document.body中而不是,Modal组件所在的DOM位置。

注意事项:

  1. Portals的事件冒泡是按照React组件树来的不是按照DOM树。也就是说,虽然子节点渲染到了其他DOM节点,但是事件还是会冒泡到父组件。
  2. createPortal有两个参数:第一个是要渲染的子节点第二个是目标DOM容器。
  3. 目标DOM容器必须是真实的DOM元素不能是React元素。

实际应用场景:

Portals在实际项目中很有用。比如弹窗对话框提示框这些组件经常需要渲染到body中避免被父元素的overflowz-index等样式影响。用Portals就能很方便地实现。

比如之前,做弹窗组件经常会遇到父元素有overflow:hidden导致弹窗被裁剪的问题。用Portals把弹窗渲染到body就不会有这个问题了。

五、自定义DOM属性

第四个新特性是自定义DOM属性。

在React 15及之前React会忽略不认识的DOM属性。如果你在组件中写了一个React不认识的属性React会直接忽略它不会渲染到DOM上。

这有时候会带来不便。比如一些第三方库需要自定义的DOM属性,或者一些新的DOM属性React还没支持就没法用。

React 16允许传递自定义的DOM属性React会把不认识的属性直接渲染到DOM上。

如何使用:

<div my-custom-attribute="value" />

在React 16中这个自定义属性会被渲染到DOM上:

<div my-custom-attribute="value"></div>

而在React 15中这个属性会被忽略。

注意事项:

  1. React还是会过滤一些属性比如以on开头的属性React会认为是,事件处理器不会作为自定义属性渲染。
  2. 自定义属性建议使用data-前缀虽然React 16支持任意自定义属性,但是按照HTML规范自定义属性应该使用data-前缀。
  3. 自定义属性是大小写不敏感的React会把属性名转成小写。

实际应用场景:

这个特性在实际项目中也很有用。比如集成第三方库的时候,一些库需要读取DOM上的自定义属性来初始化。之前,需要用ref手动设置属性现在直接在JSX中写就行。

六、setState返回null避免重复渲染

第五个新特性是setState返回null避免重复渲染。

在React 15及之前,调用setState不管状态有没有变化都会触发重新渲染。即使你setState的值和当前状态一样也会重新渲染。

这有时候会导致不必要的渲染影响性能。

React 16允许在setState的更新函数中返回null来告诉React不需要更新状态也不需要重新渲染。

如何使用:

this.setState((prevState, props) => {
  if (prevState.count === props.maxCount) {
    // 返回null不更新状态不重新渲染
    return null;
  }
  return { count: prevState.count + 1 };
});

上面的代码,如果count已经等于maxCount就返回nullReact不会更新状态也不会重新渲染。

注意事项:

  1. 这个特性只对setState的函数式更新有效如果直接传对象给setState还是会触发渲染。
  2. 返回null的时候React不会调用shouldComponentUpdate也不会触发重新渲染
  3. 这个特性能提升性能避免不必要的渲染。

实际应用场景:

这个特性在实际项目中很有用。比如一些频繁触发的事件,比如scrollresizemousemove等等在事件处理器中调用setState的时候,可以先判断状态有没有变化,如果没有变化就返回null避免不必要的渲染。

七、生命周期更新

第六个新特性是生命周期的更新。

React 16废弃了一些旧的生命周期方法引入了一些新的生命周期方法。

废弃的生命周期:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

这三个生命周期方法在React 16中被标记为不安全不推荐使用。在未来的版本中会被移除。

原因是这三个生命周期方法经常被误用导致各种问题。而且在Fiber架构下这些方法可能会被调用多次导致副作用重复执行。

新的生命周期:

  • getDerivedStateFromProps:静态方法替代componentWillReceiveProps用于根据props更新state。
  • getSnapshotBeforeUpdate:替代componentWillUpdate用于在更新之前,获取DOM的快照。

getDerivedStateFromProps:

class MyComponent extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== prevState.value) {
      return { value: nextProps.value };
    }
    return null;
  }
}

getDerivedStateFromProps是静态方法不能访问this。它接收nextProps和prevState返回要更新的state对象,或者null表示不更新。

getSnapshotBeforeUpdate:

class MyComponent extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 在更新之前,获取DOM的快照
    return this.listNode.scrollHeight;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // snapshot就是getSnapshotBeforeUpdate返回的值
    console.log(snapshot);
  }
}

getSnapshotBeforeUpdate在render之后componentDidUpdate之前,调用。它可以读取DOM的当前状态返回一个快照值这个值会作为第三个参数传给componentDidUpdate。

注意事项:

  1. 废弃的生命周期方法在React 16中还能用但是会有警告。建议尽快迁移到新的生命周期。
  2. getDerivedStateFromProps是,静态方法不能访问this也不能调用实例方法。
  3. getSnapshotBeforeUpdate需要和componentDidUpdate一起使用否则会有警告。

八、服务端渲染改进

第七个新特性是服务端渲染的改进。

React 16对服务端渲染做了很多优化性能提升了很多。

主要改进包括:

  1. 性能提升:React 16的服务端渲染比React 15快了很多据说快了3倍左右。
  2. 支持流式渲染:React 16支持renderToNodeStream和renderToStaticNodeStream能把渲染结果以流的方式返回提升首屏加载速度。
  3. 支持更多HTML属性:React 16的服务端渲染支持更多的HTML属性,比如classNamehtmlFor等等。
  4. 支持自定义属性:和客户端一样服务端渲染也支持自定义DOM属性。

流式渲染示例:

import { renderToNodeStream } from 'react-dom/server';

app.get('/', (req, res) => {
  res.write('<!DOCTYPE html><html><head><title>My App</title></head><body><div id="root">');
  const stream = renderToNodeStream(<App />);
  stream.pipe(res, { end: false });
  stream.on('end', () => {
    res.write('</div></body></html>');
    res.end();
  });
});

流式渲染能让浏览器更早地开始接收和解析HTML提升首屏加载速度特别是对于大页面效果更明显。

九、升级React 16的注意事项

最后说说升级React 16的注意事项。

如果你还在用React 15想升级到React 16需要注意以下几点:

1. 依赖更新:

React 16依赖一些新的包,比如prop-typescreate-react-class等等。如果你之前,用的是React.PropTypes需要改成prop-types包。如果你用的是React.createClass需要改成create-react-class包,或者改成ES6 class。

2. 废弃的生命周期:

React 16废弃了componentWillMountcomponentWillReceivePropscomponentWillUpdate这三个生命周期。如果你的代码中用了这些方法需要迁移到新的生命周期,或者其他方式。

3. 错误处理:

React 16的错误处理变了。之前,组件出错可能只是部分UI不显示。现在组件出错,如果没有错误边界整个组件树都会卸载。所以建议在应用的顶层加一个错误边界避免白屏。

4. 打包体积:

React 16的打包体积比React 15小了一些。因为React 16把prop-types等拆出去了核心包更小。

5. 第三方库兼容性:

升级React 16之后,需要检查一下第三方库的兼容性。一些老的库可能还没适配React 16会有问题。

十、写在最后

以上就是React 16的主要新特性和配置详解。

React 16是一个非常重要的版本Fiber架构的引入为React的未来发展打下了基础。错误边界Portals渲染数组自定义属性等等新特性也让React更强大更灵活。

如果你还没升级到React 16建议尽快升级。升级的成本不高,但是收益很大。

当然React 16也不是终点。React团队还在持续迭代未来会有更多新特性。比如React 16.3引入了新的Context APIReact 16.6引入了React.memoReact.lazy等等。

让我们一起期待React的未来发展。

希望这篇文章能帮大家更好地理解和使用React 16的新特性。如果有什么问题,或者不同的看法欢迎在评论区留言我们一起交流。