web

React 16.6 新特性

React.memo 和钩子们

Posted by Lorry on November 4, 2018

文章字数:4685, 阅读全文大约需要:13 分钟

React 团队在10月23号发布了新版本16.6.0, 添加了一些新特性.

React.memo

这个跟 React.pureComponent 很像, 只不过 pureComponent 是对 class 类的支持, 而 memo 是对 function 的支持, 可以让纯函数组件更’纯’

import React from 'react'

const MyPureComponent = React.memo(function MyComponent(props) {
    // ....
})
// or
const MyPureComponent = React.memo(props => {
    return <div>This is pure component, only change on props change</div>
})
// or if only return
const MyPureComponent = React.memo(props => (
    <div>This is pure component, only change on props change</div>
))
// reference Class
class MyPureComponent extend React.pureComponent(props) {
    render() {
        return <div>This is pure component, only change on props change</div>
    }
}

React.lazy: 利用 Suspense 进行代码分离

使用 Suspend 组件进行代码的懒加载, 如果 component 没有加载进来, 会返回 fallback 的内容, 直到完成加载为止.

import React, {lazy, Suspense} from 'react'

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

function MyComponent() {
    return (
        <Suspense fallback={<div>Loading.....</div>}>
            <OtherComponent />
        </Suspense>
    )
}

static contextType: 静态上下文

在16.3中, 提供了官方的上下文 API 作为 Legacy Context APi 的替换

const MyContext = React.creatContext();
class MyClass extends React.Component {
  static contextType = MyContext;
  componentDidMount() {
    let value = this.context;
    /* 使用 myContext 的值在完成实例化的时候产生一些副作用 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 根据 MyContext 进行渲染 */
  }
}

static getDerivedStateFromError()

React 16 中介绍了一个 Error Boundaries, 用于处理 React 渲染过程中的错误处理, 已有的生命周期 componentDidCatch方法会在错误已经发生时被触发, 这在服务端是非常好的错误日志, 也可以通过setState展示不同的用户 UI

在被出发前, 我们会渲染 null在抛出错误的 tree 中, 这个又是会破坏不期望他们的 refs 为空的父组件, 当从在服务端的 error 回复后也不会生效, 因为Did的生命周期方法不会在服务端触发渲染.

现在添加了另一个错误方法, 可以在完成渲染后渲染渲染回调的 UI. 这个方法就是getDerivedStateFromError()

在 StrictMode 中的启用语法

在16.3中介绍了 StrictMode 组件, 可以让早期的 warning 提前暴露, 以避免未来造成问题 现在新增了两个 API 在启用 API 列表里, 如果是不使用 StrictMode 的话就不用担心啦~

  • ReactDOM.findDOMNode() - 这个 API 通常被误解, 并且多数的使用是不必要的, 对于 React16的性能影响非常的大.可以使用 createRef 代替
  • Legacy Context - 使用 contextTypes 和 getChildContext, 这两个 API 的性价比很低, 这就是为什么鼓励使用新的上下文 API, 希望 contextTypes可以帮上忙.

Time Slicing

核心是异步渲染

在这一版本中, ReactJS 团队提出了一个 Time Slicing 的概念, 他可以保证类似与用户输入的高等级的更新不会被低等级的更新渲染给阻塞.

Time Slicing 可以让 ReactJS 将子组件的更新在空闲的回调中分成多个 chunks 进行计算, 并且渲染工作分散到多个 frames 中, 现在在异步渲染过程中, 它将会保证如果用户设备非常的快, app 中华的更新就会向同步一样, 而如果用户的设备不是很快, app 能够被更好的被响应, 不会有 freezing, 不会有糟糕的 UI 体验.

Suspense

suspense 功能就是 ReactJS 可以暂停任何的 state 更新, 知道这个可以被获取到的数据已经准备好 render. 本质上来说 ReactJS 延后了组件树去等待 date 被完整的获取到. 在等待的过程中, 高等级的更新(high-priority update)会继续处理, 以下是suspending 的工作流程

  • 在 render 方法中, 从缓存中读取数据
  • 如果已经被缓存, 那么 render会更正常情况一样去渲染
  • 如果值还没有缓存, 那么缓存会抛出一个 promise
  • 当 promise 被 resolve 掉之后, React 会尝试从中断点恢复.

    以下是 React Apollo’s team 在 GraphGL 查询时实现的 suspense

    const MOVIE_QUERY = gql`
    query GetMovie($id: Int!) {
      movie(id: $id) {
        id
        title
        overview
        poster_path
      }
    }
    `;
    function MovieInfo({ movie, clearActiveResult }) {
    return (
      <Query asyncMode query={MOVIE_QUERY} variables=>
        {({ data: { movie } }) => (
          <Fragment>
            <FullPoster movie={movie} />
            <h2>{movie.title}</h2>
            <div>{movie.overview}</div>
          </Fragment>
        )}
      </Query>
    );
    }
    

    Query 组件中的 asyncMode 属性允许异步渲染, 一下是Query 的 render 方法

    if(loading) {
      if(this.props.asyncMode) {
          throw this.state.queryObservable!.result();
      }
    }
    Object.assign(data.data, this.previousData, currentResult.data)
    

    async模式下, render 会抛出一个 promise, 在 suspension 期间, 开发者可以通过使用带有时间限制 props 的组件, 高效的控制加载(loading)的状态, 如下:

<Placeholder 
    delayMs={1000} 
    fallback={<Loadingsize="medium" color="blue">}>

In React, if a component suspends, we keep rendering the siblings as deeply as we can. Suspend means “don’t commit,” not “don’t reconcile.” - Andrew Clark 在 React 中, 如果一个组件延迟加载了, react 会尽可能深的加载兄弟组件, 延迟加载意味着”不要决议”, 而不是 “不要保持一致”

当然 React 的 suspense 也不是必须依赖异步渲染, 也可以使用同步模式, 但是问题就是 会立即加载, 没有任何的延迟, 这个 delayMs props 展示了为什么异步渲染对于 UX(UserExperience) 如此的重要

另一个场景是 code splitting, 代码分离. 代码分离是用于提升加载的性能, 仅仅在特定的页面按需加载代码. 代码分离将会和 suspense 属性很好的结合起来, 因为 suspense 本身可以是异步的.

import { createFetcher } from 'React';

// dynamic import
const moviePageFetcher = createFetcher(
    () => import('./MoviePage')
)

// get the MovirPage component

const moviePage = moviePageFetcher.read().default;

Profiler

这个是React插件的一个更新, 可以在交互时看到哪些组件会更新, 这样可以看到哪些时不必要的更新, 然后再SCU(shouldComponentUpdate)中进行过滤处理. 也可以进行性能分析, 修改组件的更新策略以及函数的调用方式, 在使用的时候要注意一定时生产(production)环境, 而不是开发(development)环境. 即在webpack的mode中配置.

具体请参见 React Profiler