资讯专栏INFORMATION COLUMN

精读《React16 新特性》

Nosee / 1468人阅读

摘要:引言于发布版本,时至今日已更新到,且引入了大量的令人振奋的新特性,本文章将带领大家根据更新的时间脉络了解的新特性。其作用是根据传递的来更新。新增等指针事件。

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 [
    
  • 1
  • ,
  • 2
  • ,
  • 3
  • , ]; }
    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 

    数据错误

    ; } return this.props.children; } }
    createPortal

    createPortal 的出现为 弹窗、对话框 等脱离文档流的组件开发提供了便利,替换了之前不稳定的 API unstable_renderSubtreeIntoContainer,在代码使用上可以做兼容,如:

    const isReact16 = ReactDOM.createPortal !== undefined;
    
    const getCreatePortal = () =>
      isReact16
        ? ReactDOM.createPortal
        : 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);
    
        this.node = document.createElement("div");
        document.body.appendChild(this.node);
      }
    
      render() {
        return createPortal(
          
    {this.props.children}
    , this.node ); } }
    支持自定义 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 的第二个回调函数参数会被调用,这个回调函数返回的是真正渲染子组件的元素;

    针对普通场景来说,react-call-return 有点过度设计的感觉,但是如果针对一些特定场景的话,它的作用还是非常明显,比如,在渲染瀑布流布局时,利用 react-call-return 可以先缓存子组件的 ReactElement,等必要的信息足够之后父组件再触发 render,完成渲染。

    import React from "react";
    import { unstable_createReturn, unstable_createCall } from "react-call-return";
    
    const Child = (props) => {
      return unstable_createReturn({
        size: props.children.length,
        renderItem: (partSize, totalSize) => {
          return 
    { props.children } { partSize } / { totalSize }
    ; } }); }; const Parent = (props) => { return (
    { unstable_createCall( props.children, (props, returnValues) => { const totalSize = returnValues.map(v => v.size).reduce((a, b) => a + b, 0); return returnValues.map(({ size, renderItem }) => { return renderItem(size, totalSize); }); }, props ) }
    ); };
    React v16.2 Fragment

    Fragment 组件其作用是可以将一些子元素添加到 DOM tree 上且不需要为这些元素提供额外的父节点,相当于 render 返回数组元素。

    render() {
      return (
        
          Some text.
          

    A heading

    More text.

    Another heading

    Even more text.
    ); }
    React v16.3 createContext

    全新的 Context API 可以很容易穿透组件而无副作用,其包含三部分:React.createContext,Provider,Consumer。

    React.createContext 是一个函数,它接收初始值并返回带有 Provider 和 Consumer 组件的对象;

    Provider 组件是数据的发布方,一般在组件树的上层并接收一个数据的初始值;

    Consumer 组件是数据的订阅方,它的 props.children 是一个函数,接收被发布的数据,并且返回 React Element;

    const ThemeContext = React.createContext("light");
    
    class ThemeProvider extends React.Component {
      state = {theme: "light"};
    
      render() {
        return (
          
            {this.props.children}
          
        );
      }
    }
    
    class ThemedButton extends React.Component {
      render() {
        return (
          
            {theme => 
    createRef / forwardRef

    React16 规范了 Ref 的获取方式,通过 React.createRef 取得 Ref 对象。

    // before React 16
    ···
    
      componentDidMount() {
        const el = this.refs.myRef
      }
    
      render() {
        return 
    } ··· // React 16+ constructor(props) { super(props) this.myRef = React.createRef() } render() { return
    } ···

    React.forwardRef 是 Ref 的转发, 它能够让父组件访问到子组件的 Ref,从而操作子组件的 DOM。 React.forwardRef 接收一个函数,函数参数有 props 和 ref。

    const TextInput = React.forwardRef((props, ref) => (
      
    ))
    
    const inputRef = React.createRef()
    
    class App extends Component {
      constructor(props) {
        super(props)
        
        this.myRef = React.createRef()
      }
    
      handleSubmit = event => {
        event.preventDefault()
        
        alert("input value is:" + inputRef.current.value)
      }
      
      render() {
        return (
          
    ) } }
    生命周期函数的更新

    React16 采用了新的内核架构 Fiber,Fiber 将组件更新分为两个阶段:Render Parse 和 Commit Parse,因此 React 也引入了 getDerivedStateFromProps 、 getSnapshotBeforeUpdate 及 componentDidCatch 等三个全新的生命周期函数。同时也将 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 标记为不安全的方法。

    static getDerivedStateFromProps(nextProps, prevState)

    getDerivedStateFromProps(nextProps, prevState) 其作用是根据传递的 props 来更新 state。它的一大特点是无副作用,由于处在 Render Phase 阶段,所以在每次的更新都会触发该函数, 在 API 设计上采用了静态方法,使其无法访问实例、无法通过 ref 访问到 DOM 对象等,保证了该函数的纯粹高效。

    为了配合未来的 React 异步渲染机制,React v16.4 对 getDerivedStateFromProps 做了一些改变, 使其不仅在 props 更新时会被调用,setState 时也会被触发。

    如果改变 props 的同时,有副作用的产生,这时应该使用 componentDidUpdate;

    如果想要根据 props 计算属性,应该考虑将结果 memoization 化;

    如果想要根据 props 变化来重置某些状态,应该考虑使用受控组件;

    static getDerivedStateFromProps(props, state) {
      if (props.value !== state.controlledValue) {
        return {
          controlledValue: props.value,
        };
      }
      
      return null;
    }
    getSnapshotBeforeUpdate(prevProps, prevState)

    getSnapshotBeforeUpdate(prevProps, prevState) 会在组件更新之前获取一个 snapshot,并可以将计算得的值或从 DOM 得到的信息传递到 componentDidUpdate(prevProps, prevState, snapshot) 函数的第三个参数,常常用于 scroll 位置定位等场景。

    componentDidCatch(error, info)

    componentDidCatch 函数让开发者可以自主处理错误信息,诸如错误展示,上报错误等,用户可以创建自己的 Error Boundary 来捕获错误。

    componentWillMount(nextProps, nextState)

    componentWillMount 被标记为不安全,因为在 componentWillMount 中获取异步数据或进行事件订阅等操作会产生一些问题,比如无法保证在 componentWillUnmount 中取消掉相应的事件订阅,或者导致多次重复获取异步数据等问题。

    componentWillReceiveProps(nextProps) / componentWillUpdate(nextProps, nextState)

    componentWillReceiveProps / componentWillUpdate 被标记为不安全,主要是因为操作 props 引起的 re-render 问题,并且对 DOM 的更新操作也可能导致重新渲染。

    Strict Mode

    StrictMode 可以在开发阶段开启严格模式,发现应用存在的潜在问题,提升应用的健壮性,其主要能检测下列问题:

    识别被标志位不安全的生命周期函数

    对弃用的 API 进行警告

    探测某些产生副作用的方法

    检测是否使用 findDOMNode

    检测是否采用了老的 Context API

    class App extends React.Component {
      render() {
        return (
          
    ) } }
    React v16.4 Pointer Events

    指针事件是为指针设备触发的 DOM 事件。它们旨在创建单个 DOM 事件模型来处理指向输入设备,例如鼠标,笔 / 触控笔或触摸(例如一个或多个手指)。指针是一个与硬件无关的设备,可以定位一组特定的屏幕坐标。拥有指针的单个事件模型可以简化创建 Web 站点和应用程序,并提供良好的用户体验,无论用户的硬件如何。但是,对于需要特定于设备的处理的场景,指针事件定义了一个 pointerType 属性,用于检查产生事件的设备类型。

    React 新增 onPointerDown / onPointerMove / onPointerUp / onPointerCancel / onGotPointerCapture / onLostPointerCapture / onPointerEnter / onPointerLeave / onPointerOver / onPointerOut 等指针事件。

    这些事件只能在支持 指针事件 规范的浏览器中工作。如果应用程序依赖于指针事件,建议使用第三方指针事件 polyfill。

    React v16.5 Profiler

    React 16.5 添加了对新的 profiler DevTools 插件的支持。这个插件使用 React 的 Profiler 实验性 API 去收集所有 component 的渲染时间,目的是为了找出 React App 的性能瓶颈,它将会和 React 即将发布的 时间片 特性完全兼容。

    React v16.6 memo

    React.memo() 只能作用在简单的函数组件上,本质是一个高阶函数,可以自动帮助组件执行shouldComponentUpdate(),但只是执行浅比较,其意义和价值有限。

    const MemoizedComponent = React.memo(props => {
      /* 只在 props 更改的时候才会重新渲染 */
    });
    lazy / Suspense

    React.lazy() 提供了动态 import 组件的能力,实现代码分割。

    Suspense 作用是在等待组件时 suspend(暂停)渲染,并显示加载标识。

    目前 React v16.6 中 Suspense 只支持一个场景,即使用 React.lazy() 和 实现的动态加载组件。

    import React, {lazy, Suspense} from "react";
    const OtherComponent = lazy(() => import("./OtherComponent"));
    
    function MyComponent() {
      return (
        Loading...
    }> ); } static contextType

    static contextType 为 Context API 提供了更加便捷的使用体验,可以通过 this.context 来访问 Context。

    const MyContext = React.createContext();
    
    class MyClass extends React.Component {
      static contextType = MyContext;
      
      componentDidMount() {
        const value = this.context;
      }
      
      componentDidUpdate() {
        const value = this.context;
      }
      
      componentWillUnmount() {
        const value = this.context;
      }
      
      render() {
        const value = this.context;
      }
    }
    getDerivedStateFromError

    static getDerivedStateFromError(error) 允许开发者在 render 完成之前渲染 Fallback UI,该生命周期函数触发的条件是子组件抛出错误,getDerivedStateFromError 接收到这个错误参数后更新 state。

    class ErrorBoundary extends React.Component {
      state = { hasError: false };
      
      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 

    Something went wrong.

    ; } return this.props.children; } }
    React v16.7(~Q1 2019) Hooks

    Hooks 要解决的是状态逻辑复用问题,且不会产生 JSX 嵌套地狱,其特性如下:

    多个状态不会产生嵌套,依然是平铺写法;

    Hooks 可以引用其他 Hooks;

    更容易将组件的 UI 与状态分离;

    Hooks 并不是通过 Proxy 或者 getters 实现,而是通过数组实现,每次 useState 都会改变下标,如果 useState 被包裹在 condition 中,那每次执行的下标就可能对不上,导致 useState 导出的 setter 更新错数据。

    更多 Hooks 使用场景可以阅读下列文章:

    精读《怎么用 React Hooks 造轮子》

    function App() {
      const [open, setOpen] = useState(false);
      
      return (
        <>
          
           setOpen(false)}
            onCancel={() => setOpen(false)}
          />
        
      );
    }
    React v16.8(~Q2 2019) Concurrent Rendering

    Concurrent Rendering 并发渲染模式是在不阻塞主线程的情况下渲染组件树,使 React 应用响应性更流畅,它允许 React 中断耗时的渲染,去处理高优先级的事件,如用户输入等,还能在高速连接时跳过不必要的加载状态,用以改善 Suspense 的用户体验。

    目前 Concurrent Rendering 尚未正式发布,也没有详细相关文档,需要等待 React 团队的正式发布。

    React v16.9(~mid 2019) Suspense for Data Fetching

    Suspense 通过 ComponentDidCatch 实现用同步的方式编写异步数据的请求,并且没有使用 yield / async / await,其流程:调用 render 函数 -> 发现有异步请求 -> 暂停渲染,等待异步请求结果 -> 渲染展示数据。

    无论是什么异常,JavaScript 都能捕获,React就是利用了这个语言特性,通过 ComponentDidCatch 捕获了所有生命周期函数、render函数等,以及事件回调中的错误。如果有缓存则读取缓存数据,如果没有缓存,则会抛出一个异常 promise,利用异常做逻辑流控制是一种拥有较深的调用堆栈时的手段,它是在虚拟 DOM 渲染层做的暂停拦截,代码可在服务端复用。

    import { fetchMovieDetails } from "../api";
    import { createFetch } from "../future";
    
    const movieDetailsFetch = createFetch(fetchMovieDetails);
    
    function MovieDetails(props) {
      const movie = movieDetailsFetch.read(props.id);
    
      return (
        
    ); }
    4 总结

    从 React16 的一系列更新和新特性中我们可以窥见,React 已经不仅仅只在做一个 View 的展示库,而是想要发展成为一个包含 View / 数据管理 / 数据获取 等场景的前端框架,以 React 团队的技术实力以及想法,笔者还是很期待和看好 React 的未来,不过它渐渐地已经对开发新手们不太友好了。

    5 更多讨论
    讨论地址是:[精读《React16 新特性》 · Issue #115 · dt-fe/weekly]https://github.com/dt-fe/week...
    

    如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/99869.html

    相关文章

    • 精读React Hooks》

      摘要:更容易将组件的与状态分离。也就是只提供状态处理方法,不会持久化状态。大体思路是利用共享一份数据,作为的数据源。精读带来的约定函数必须以命名开头,因为这样才方便做检查,防止用判断包裹语句。前端精读帮你筛选靠谱的内容。 1 引言 React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想尝试的同学安装此版本即可。 React Hooks 要解决的问题是状态共享,...

      kohoh_ 评论0 收藏0
    • 精读《Vue3.0 Function API》

      摘要:拿到的都是而不是原始值,且这个值会动态变化。精读对于的与,笔者做一些对比。因此采取了作为优化方案只有当第二个依赖参数变化时才返回新引用。不需要使用等进行性能优化,所有性能优化都是自动的。前端精读帮你筛选靠谱的内容。 1. 引言 Vue 3.0 的发布引起了轩然大波,让我们解读下它的 function api RFC 详细了解一下 Vue 团队是怎么想的吧! 首先官方回答了几个最受关注的...

      voyagelab 评论0 收藏0
    • 精读《Typescript 3.2 特性

      摘要:引言发布了几个新特性,主要变化是类型检查更严格,对一些时髦功能拓展了类型支持。精读这次改动意图非常明显,是为了跟上的新语法。基本可以算是对社区的回馈。讨论地址是精读新特性如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。 1 引言 Typescript 3.2 发布了几个新特性,主要变化是类型检查更严格,对 ES6、ES7 一些时髦功能拓展了类型支持。 2 概要 下面挑一...

      cucumber 评论0 收藏0
    • 精读React PowerPlug 源码》

      摘要:今天我们就来解读一下的源码。比较有意思,将定时器以方式提供出来,并且提供了方法。实现方式是,在组件内部维护一个定时器,实现了组件更新销毁时的计时器更新销毁操作,可以认为这种定时器的生命周期绑定了组件的生命周期,不用担心销毁和更新的问题。 1. 引言 React PowerPlug 是利用 render props 进行更好状态管理的工具库。 React 项目中,一般一个文件就是一个类,...

      teren 评论0 收藏0
    • 精读《源码学习》

      摘要:精读原文介绍了学习源码的两个技巧,并利用实例说明了源码学习过程中可以学到许多周边知识,都让我们受益匪浅。讨论地址是精读源码学习如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。 1. 引言 javascript-knowledge-reading-source-code 这篇文章介绍了阅读源码的重要性,精读系列也已有八期源码系列文章,分别是: 精读《Immer.js》源...

      aboutU 评论0 收藏0

    发表评论

    0条评论

    Nosee

    |高级讲师

    TA的文章

    阅读更多
    最新活动
    阅读需要支付1元查看
    <