Fiber 架构

Reconciler(协调):跟踪组件的状态变化,并将更新的状态映射到到新的界面。React 的元素、生命周期、 render 方法,以及应用于组件子元素的 diffing 算法综合起到的作用

Stack Reconciler

V16- 中更新过程是同步的,React 挂载或者更新的时候,递归调用各个组件生命周期,对比VDOM 更新 DOM 树,整个过程一旦开始,不可被打断。一个调用链很长并且计算量很大的任务的调用栈。

  • 浏览器主线程被渲染过程占用比较长的时间,导致进程阻塞

  • 渲染过程没有优先级可言,不同状态变化是需要有优先级的

React 16 - 图1

Fiber Reconciler

Fiber 把渲染过程分成了一个个 chunk,变同步为异步,提升性能:

  • 增量渲染:将渲染任务拆分成块,匀到多帧

  • 渲染过程中有机会将主线程交给 react 以外的 JS 调用(如用户操作)

  • 渲染有了优先级,能让高优先级的工作先做

React 16 - 图2

Fiber 之后,将更新分了两个部分

React 16 - 图3

render/reconciliation(可中断)

  • componentWillMount

  • componentWillReceiveProps

  • shouldComponentUpdate

  • componentWillUpdate

commit(不可中断)

  • componentDidMount

  • componentDidUpdate

  • componentWillUnmount

新增了一个顶级 API: ReactDOM.createPortal

  • Portal 本身不是新的概念,在以往使用 React 实现模态框等组件时我们也会用到,现在 React 官方提供了 API 使得能够更方便地实现这样的功能。

  • 在一般的 React 结构中,组件的嵌套关系和渲染出来的 DOM 的嵌套关系是一致的(子组件渲染出的 DOM 一定是在父组件渲染出的 DOM 的内部的)。

  • 但某些情况下,这样的限制会导致问题,例如实现一个模态框(Modal),虽然模态框所在的组件在它的父组件内部,但是通常需要被渲染在 body 元素下。 新的 API 使用方式如下:

  1. render() {
  2. // React 会在你提供的 domNode 下渲染,而不是在当前组件所在的 DOM
  3. return ReactDOM.createPortal(
  4. this.props.children,
  5. domNode,
  6. );
  7. }

更多信息可以查看 Portals

已改变的生命周期

React 16 - 图4

即将移除的

  • componentWillMount: UNSAFE_componentWillMount

  • componentWillReceiveProps: UNSAFE_componentWillReceiveProps

  • componentWillUpdate: UNSAFE_componentWillUpdate

新增的

  • getDerivedStateFromProps(props, state):

  • getDerivedStateFromError()

在发布Error Boundaries的时候,React提供了一个新的生命周期方法componentDidCatch,在捕获到错误的时候会触发,你可以在里面修改state以显示错误提醒的UI,或者将错误信息发送给服务端进行log用于后期分析。但是这里有个问题,就是在捕获到错误的瞬间,React会在这次渲染周期中将这个组件渲染为null,这就有可能导致他的父组件设置他上面的ref获得null而导致一些问题,所以现在提供了这个方法。

这个方法跟getDerivedStateFromProps类似,唯一的区别是他只有在出现错误的时候才触发,他相对于componentDidCatch的优势是在当前的渲染周期中就可以修改state,以在当前渲染就可以出现错误的UI,而不需要一个null的中间态。

而这个方法的出现,也意味着以后出现错误的时候,修改state应该放在这里去做,而后续收集错误信息之类的放到componentDidCatch里面。

  • getSnapshotBeforeUpdate(prevProps, prevState):

更好的错误处理方式

V16-:handleError(error)

  1. unstable_handleError: function(error){
  2. this.setState({error: "oops"})
  3. }
  • 只能处理 render() 抛出 Error,其他的生命周期函数触发不了

  • 能捕获当前组件本身抛出的 Error,但是还是会阻碍接下来的渲染

V 16+:componentDidCatch(error, info )

  • error: 捕获的错误

  • info: 错误信息栈

  1. // info
  2. componentStack: "
  3. in BuggyCounter (created by App)
  4. in ErrorBoundary (created by App)
  5. in div (created by App)
  6. in App"

使用方法

  1. class ErrorBoundary extends React.Component {
  2. constructor(props) {
  3. super(props)
  4. this.state = { hasError: false }
  5. }
  6. componentDidCatch(err, info) {
  7. this.setState({ hasError: false })
  8. // 上报错误
  9. logErrorToMyService(err, info)
  10. }
  11. render() {
  12. if (this.state.hasError) {
  13. return <h1>Something is wrong</h1>
  14. }
  15. return this.props.children
  16. }
  17. }
  1. <ErrorBoundary>
  2. <otherComponent></otherComponent>
  3. <ErrorBoundary>

ErrorBoundary 作为一个专门处理错误的异常边界组件

  • 能捕获所有子组件,任何生命周期函数抛出的异常

  • 只能捕获子组件抛出的 error,组件本身的异常无法捕获。因此我们的异常边界组件不能涉及业务代码,否则将无法捕获自身逻辑错误

子组件抛出的错误会层层往上传递,直到被捕获,一到捕获之后,就不会再往上传播。

  • 注意:Error boundaries 不能捕获的错误

  • 事件处理

  • 异步(e.g.setTimeout or requestAnimationFrame callbacks)

  • 服务端渲染

  • 组件自身的错误

错误处理表现

在 React16 之后,未被捕获的错误,错误会一直往上抛,直到 React 将 React Tree 全部卸载。同时,在开发环境,控制台会输出所有的错误,包括被捕获的错误。

Error Handling in React 16

新的 Render 返回

  • React elements

  • Booleans OR null

  • V16.0.0 Arrays and V16.2.0 Fragments

  1. render() {
  2. // 不需要再包裹一层了!
  3. return [
  4. // 但是要使用 key
  5. <li key="A">First item</li>,
  6. <li key="B">Second item</li>,
  7. <li key="C">Third item</li>,
  8. ];
  9. }
  1. render() {
  2. return 'Look ma, no spans!';
  3. }

更小的体积

  • V16-, react + react-dom 在 gizp 后的总体积为 109KB,V16 比之前版本的 161.7KB 减小了 30%

部分非核心模块被移出

  • React.createClassReact.PropTypes 等模块被移出了 react 包,现在你必须从单独的包里引入。

基于 MIT 协议

  • 现在 React 15.6.2 和 React 16+ 都是基于 MIT 协议了,你可以自由选择

lazy

React 16.6
配合发布了一个 Suspense 组件,也就是最开始命名为 Placeholder 的组件,用来配合 render 方法内部的异步操作的,让我们先来看一下 lazy 的用法

  1. import React, {lazy, Suspense} from 'react';
  2. const OtherComponent = lazy(() => import('./OtherComponent'));
  3. function MyComponent() {
  4. return (
  5. <Suspense fallback={<div>Loading...</div>}>
  6. <OtherComponent />
  7. </Suspense>
  8. );
  9. }

然而这并不仅仅 Suspense 的唯一用处,React正式开放 Suspense 组件代表着所有异步的操作都可以在 render 方法里面做了
Suspense能够让加载数据的操作变得异常简单。Dan 在冰岛的分享,现在npm上也有一个包提供数据加载的功能了,simple-cache-provider,但是现在还不要在正式环境使用他哦。

React.memo

ClassComponent可以通过继承类PureComponent或者实现shouldComponentUpdate来主动判断组件是否需要重新渲染,以此来提高性能,但是FunctionalComponent到目前为止没有类似的功能。

所以今天React发布了React.memo方法,来实现类似PureComponent的功能,即浅比较props是否有变化,如果没有变化,就不重新渲染当前组件

  1. const FunctionalComponent = React.memo((props) => {
  2. })

参考资源

Lin Clark 的演讲视频React Fiber是什么完全理解React Fiber
Inside Fiber: in-depth overview of the new reconciliation algorithm in React