Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

# react 16 生命周期的变化 #9

Open
oliver1204 opened this issue Dec 12, 2018 · 0 comments
Open

# react 16 生命周期的变化 #9

oliver1204 opened this issue Dec 12, 2018 · 0 comments

Comments

@oliver1204
Copy link
Owner

oliver1204 commented Dec 12, 2018

react 16 之前的生命周期图

react 16.4 之后版本的生命周期

为什么我们这里直接跳过 16.0 ~ 16.3 版本,直接说16.4呢?原因是据官方的说法,在新的生命周期 getDerivedStateFromProps react 自身考虑失误,在16.4 纠正了这个错误,所以我们直接来谈 16.4, 下文中的16 之后的版本,也指的是 16.4 之后的版本。

通过两张图的对比我们可以看到,react 16 前后版本的差别:

componentWillMount
componentWillReceiveProps ——> getDerivedStateFromProps(nextProps, prevState)
componentWillUpdate

getDerivedStateFromProps是一个静态方法,所以不能在这个函数里面使用this,这个函数有两个参数props和state,分别指接收到的新参数和当前的state对象,这个函数会返回一个对象用来更新当前的state对象,如果不需要更新可以返回null。

react 16 + 生命周期的变化

新增了getDerivedStateFromProps 、getSnapshotBeforeUpdate 、componentDidCatch(捕获报错用的,Suspense的实现原理) 三个生命周期,删除了componentWillMount、componentWillReceiveProps、componentWillUpdate三个生命周期。

为什么要这样改变?

在 react 16 中 react 公布了新的API -- Time Slice 和 Suspense.

Time Slice 时间分片

React16 版本前是如何更新的

React 在16版本之前 DOM 更新的做法是 边 比较DOM结构更新的,如果发现有变化, 则立即做出响应。

下面让我们可以想象这样的例子,

image

A 节点下面有无数的儿子节点在变化, 恰巧此时我们又在 input 输入的时候,这时,就会造成react 卡在这里,不停的算不停的算,从而整个进程被卡住。

但是现在,我们做到了 Slicing(异步渲染机制), 将「比较」和「更新」分开进行。
比较过程中,(在指定时间段内 猜是16毫秒)当比较完当前节点后,它看看是否还有时间比较下一个节点,如果有的话才会继续比较,如果没有的话,把主线程释放出来,给更紧急的任务;如果真的有更紧急的任务,前面的比较内容会被丢弃。然后等待变化全部结束后,一次性的更新所有内容。

react16 版本之后的渲染过程

总结一下,react16 的渲染更新分为两个阶段。
第一阶段(reconciliation阶段)比较变化: 这一阶段做的是Fiber的update,然后产出的是effect list(可以想象成将老的View更新到新的状态所需要做的DOM操作的列表)。这一个阶段是没有副作用的,因此这个过程可以被打断,然后恢复执行。

第二阶段(commit阶段 ) 更新不变化,不会别打断。Reconciliation产生的effect list只有在commit之后才会生效,也就是真正应用到DOM中。这一阶段往往不会执行太长时间,因此是同步的,这样也避免了组件内视图层结构和DOM不一致。这样就会带来一个问题:在 react16 之前的版本,经常会有人喜欢在 componentWillMount 时调用 ajax 函数来获取数据,这样造成数据别重复获取。

为了解决这个问题, 就出现了上面的生命周期的变化 --- 废弃 componentWillMount、componentWillReceiveProps、componentWillUpdate 以 getDerivedStateFromProps 取而代之。

上面提到了 getDerivedStateFromProps 是一个静态函数, 之所以把它设置成一个 静态函数 的目的是:react 不希望你在 第一阶段做任何无用的操作,只需专心的做好state 计算方面的事情。

Suspense 悬停(也可以叫做暂停)

以上本文的主要内容就讲完了,但是前文提到了 Dan 老哥在提出 Time Slice 的同时,还提出了Suspense。下面我们简单介绍一下它。

Suspense 的用法与我们之前书写组件的方式有很大的差异,因为我们知道,在react的render()函数中,异步去fetch数据是没用的,因为异步函数会在下一个JS事件循环中才会进行,此时,已经渲染完毕了。所以拿到数据了也没用。

Suspense 的实现过程

  • 在render函数中,我们可以写入一个异步请求,请求数据
  • react会从我们缓存中读取这个缓存
  • 如果有缓存了,直接进行正常的render
  • 如果没有缓存,那么会抛出一个异常,这个异常是一个promise当这个promise完成后(请求数据完成),react会继续回到原来的render中(实际上是重新执行一遍render),把数据render出来
  • 完全同步写法,没有任何异步callback之类的东西

或者也可以简单的总结为下面的过程:

调用render函数->发现有异步请求->悬停,等待异步请求结果->再渲染展示数据

由此可以看到,我们可以用一种同步的方式去书写代码,就像我们写async/await一样!

  • 引入新的api,可以使得任何state更新暂停,直到条件满足时,再渲染(像async/await)
  • 可以在任何一个组件里放置异步获取数据,而不用做多余的设置
  • 在网速非常快的时候,可设置,整个数据到达Dom,更新完毕以后再渲染
  • 在网速非常慢的时候,可设置,精确到单个组件的等待、以及更新,然后再渲染

看一个Suspense 的例子:

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

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

在我们的业务场景中,OtherComponent可以代表多个条件渲染组件,我们全部加载完成才取消loding。

只要promise没执行到resolve,suspense都会返回fallback中的loading。

代码简洁,loading可提升至祖先组件,易聚合。相当优雅的解决了条件渲染。

Suspense的实现与探讨

react 生命周期图片原稿

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant