React16 新特性
1 引言
于 2017.09.26 Facebook 发布 React v16.0 版本,时至今日已更新到 React v16.6,且引入了大量的令人振奋的新特性,本文章将带领大家根据 React 更新的时间脉络了解 React16 的新特性。
2 概述
按照 React16 的更新时间,从 React v16.0 ~ React v16.6 进行概述。
React v16.0
•render 支持返回数组和字符串、Error Boundaries、createPortal、支持自定义 DOM 属性、减少文件体积、fiber;
React v16.1
•react-call-return;
React v16.2
•Fragment;
React v16.3
•createContext、createRef、forwardRef、生命周期函数的更新、Strict Mode;
React v16.4
•Pointer Events、update getDerivedStateFromProps;
React v16.5
•Profiler;
React v16.6
•memo、lazy、Suspense、static contextType、static getDerivedStateFromError();
React v16.7(~Q1 2019)
•Hooks;
React v16.8(~Q2 2019)
•Concurrent Rendering;
React v16.9(~mid 2019)
•Suspense for Data Fetching;
下面将按照上述的 React16 更新路径对每个新特性进行详细或简短的解析。
3 精读
React v16.0
render 支持返回数组和字符串
// 不需要再将元素作为子元素装载到根元素下面
render() {
  return [
    <li/>1</li>,
    <li/>2</li>,
    <li/>3</li>,
  ];
}
Error Boundaries
React15 在渲染过程中遇到运行时的错误,会导致整个 React 组件的崩溃,而且错误信息不明确可读性差。React16 支持了更优雅的错误处理策略,如果一个错误是在组件的渲染或者生命周期方法中被抛出,整个组件结构就会从根节点中卸载,而不影响其他组件的渲染,可以利用 error boundaries 进行错误的优化处理。
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  componentDidCatch(error, info) {
    this.setState({ hasError: true });

    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>数据错误</h1>;
    }

    return this.props.children;
  }
}
createPortal
createPortal 的出现为 弹窗、对话框 等脱离文档流的组件开发提供了便利,替换了之前不稳定的 API unstable_renderSubtreeIntoContainer,在代码使用上可以做兼容,如:
const isReact16 = atePortal !== undefined;

const getCreatePortal = () =>
  isReact16
    ? atePortal
    : ReactDOM.unstable_renderSubtreeIntoContainer;
使用 createPortal 可以快速创建 Dialog 组件,且不需要牵扯到 componentDidMount、componentDidUpdate 等生命周期函数。
并且通过 createPortal 渲染的 DOM,事件可以从 portal 的入口端冒泡上来,如果入口端存在 onDialogClick 等事件,createPortal 中的 DOM 也能够被调用到。
import React from "react";
import { createPortal } from "react-dom";

class Dialog extends React.Component {
  constructor() {
    super(props);

reacthooks理解    de = ateElement("div");
    document.body.de);
  }

  render() {
    return createPortal(<div>{this.props.children}</div>, de);
  }
}
支持自定义 DOM 属性
以前的 React 版本 DOM 不识别除了 HTML 和 SVG 支持的以外属性,在 React16 版本中将会把全部的属性传递给 DOM 元素。这个新特性可以让我们摆脱可用的 React DOM 属性白名单。笔者之前写过一个方法,用于过滤非 DOM 属性 filter-react-dom-props,16 之后即可不再需要这样的方法。
减少文件体积
React16 使用 Rollup 针对不同的目标格式进行代码打包,由于打包工具的改变使得库文件大小得到缩减。
•React 库大小从 20.7kb(压缩后 6.9kb)降低到 5.3kb(压缩后 2.2kb)
•ReactDOM 库大小从 141kb(压缩后 42.9kb)降低到 103.7kb(压缩后 32.6kb)
•React + ReactDOM 库大小从 161.7kb(压缩后 49.8kb)降低到 109kb(压缩后 43.8kb)
Fiber
Fiber 是对 React 核心算法的一次重新实现,将原本的同步更新过程碎片化,避免主线程的
长时间阻塞,使应用的渲染更加流畅。
在 React16 之前,更新组件时会调用各个组件的生命周期函数,计算和比对 Virtual DOM,更新 DOM 树等,这整个过程是同步进行的,中途无法中断。当组件比较庞大,更新操作耗时较长时,就会导致浏览器唯一的主线程都是执行组件更新操作,而无法响应用户的输入或动画的渲染,很影响用户体验。
Fiber 利用分片的思想,把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,在每个小片执行完之后,就把控制权交还给 React 负责任务协调的模块,如果有紧急任务就去优先处理,如果没有就继续更新,这样就给其他任务一个执行的机会,唯一的线程就不会一直被独占。
因此,在组件更新时有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。所以 React Fiber 把一个更新过程分为两个阶段:
•第一个阶段 Reconciliation Phase,Fiber 会出需要更新的 DOM,这个阶段是可以被打断的;
•第二个阶段 Commit Phase,是无法被打断的,完成 DOM 的更新并展示;
在使用 Fiber 后,需要检查与第一阶段相关的生命周期函数,避免逻辑的多次或重复调用:
•componentWillMount
•componentWillReceiveProps
•shouldComponentUpdate
•componentWillUpdate
与第二阶段相关的生命周期函数:
•componentDidMount
•componentDidUpdate
•componentWillUnmount
React v16.1
Call Return(react-call-return npm)
react-call-return 目前还是一个独立的 npm 包,主要是针对 父组件需要根据子组件的回调信息去渲染子组件场景 提供的解决方案。
在 React16 之前,针对上述场景一般有两个解决方案:
•首先让子组件初始化渲染,通过回调函数把信息传给父组件,父组件完成处理后更新子组件 props,触发子组件的第二次渲染才可以解决,子组件需要经过两次渲染周期,可能会造成渲染的抖动或闪烁等问题;
•首先在父组件通过 children 获得子组件并读取其信息,利用 React.cloneElement 克隆产生新元素,并将新的属性传递进去,父组件 render 返回的是克隆产生的子元素。虽然这种方法只需要使用一个生命周期,但是父组件的代码编写会比较麻烦;
React16 支持的 react-call-return,提供了两个函数 unstable_createCall 和 unstable_createReturn,其中 unstable_createCall 是 父组件使用,unstable_createReturn 是 子组件使用,父组件发出 Call,子组件响应这个 Call,即 Return。
•在父组件 render 函数中返回对 unstable_createCall 的调用,第一个参数是 props.children,第二个参数是一个回调函数,用于接受子组件响应 Call 所返回的信息,第三个参数是 props;
•在子组件 render 函数返回对 unstable_createReturn 的调用,参数是一个对象,这个对象会在 unstable_createCall 第二个回调函数参数中访问到;
•当父组件下的所有子组件都完成渲染周期后,由于子组件返回的是对 unstable_createReturn 的调用所以并没有渲染元素,unstable_createCall 的第二个回调函数参数会被调用,这个回调函数返回的是真正渲染子组件的元素;