博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从React16生命周期到React fiber架构
阅读量:4083 次
发布时间:2019-05-25

本文共 7581 字,大约阅读时间需要 25 分钟。

React 16 新的生命周期


之前回看React文档时,发现React已经更新到了v16.7.0版本,想起之前官方所提过的将会在未来v17.0 版本中移除componentWillMountcomponentWillReceivePropscomponentWillUpdate,因此在这里简单记录一下移除的三个生命周期函数,和学习新引入的两个生命周期函数 getDerivedStateFromPropsgetSnapshotBeforeUpdate

componentWillMount 对于这个生命周期函数,以前的我最喜欢拿来干两件事,一是在这个函数里进行状态的赋值,和请求异步加载的数据,但是前者完全可以在constructor中完成,因为本身他就是用于状态的初始化的,而后者由于componentWillMount 之后会立刻执行render,所以在第一次render中并不能拿到异步数据。

使用getDerivedStateFromProps配合componentDidUpdate可以替代componentWillReceiveProps的所有功能,this.setState并不会触发这个函数,每次得到了新的props后会触发这个生命周期,用于更新state。值得注意的是getDerivedStateFromProps是一个static方法,因此我们在其内部并不能使用this.setState方法去更新状态。 如果需要对比 prevProps 需要单独在 state 中维护

componentWillReceiveProps(nextProps) {      if (nextProps.cards !== this.props.cards) {          this.setState({              filterTableData: this.GetAllCard(nextProps.cards),          })      }  }  static getDerivedStateFromProps(nextProps, prevState){    if(nextProps.cards !== prevState.filterTableData){      return{        filterTableData :nextProps.cards      }    }    return null  }复制代码

我在使用componentWillReceiveProps的另一个场景是在props改变使执行某些回调,在新的生命周期函数中,我们可以将这一部分功能加在componentDidUpdate中

我们可以使用getSnapshotBeforeUpdatecomponentDidUpdate替代componentWillUpdate,其实在平常工作中我基本不用这个生命周期函数,不过既然遇到就在这简单记录一下 由于getSnapshotBeforeUpdaterender之后,但在节点挂载前,可以通过getSnapshotBeforeUpdate方法,获取dom信息,然后通过componentDidUpdate反映,前者返回的dom可以在componentDidUpdate的第三个参数(prevProps, prevState, snapshot)中获取到。


附上新的

生命周期图

 

 

为什么需要新的生命周期

上面介绍了React16带来的新的生命周期,那么为什么需要修改之前的生命周期呢? 是因为新的react引入了异步渲染机制,主要的功能是,在渲染完成前,可以中断任务,中断之后不会继续执行生命周期,而是重头开始执行生命周期。这导致上述的componentWillMountcomponentWillReceivePropscomponentWillUpdate可能会被中断,导致执行多次,带来意想不到的情况。

为什么需要异步渲染

我们都知道在react16之前,react对virtual dom 的渲染是同步的,即每次将所有操作累加起来,统计对比出所有的变化后,统一更新一次DOM树(),随着组件层级的深入,由于渲染更新一旦开始就无法停止,导致主线程长时间被占用,这也是react在动画,布局和手势等区域会有造成掉帧、延迟响应(甚至无响应)等不佳体验。

React Fiber


目的

为了解决这个问题,react推出了Fiber,它能够将渲染工作分割成块并将其分散到多个帧中。同时加入了在新更新进入时暂停,中止或重复工作的能力和为不同类型的更新分配优先级的能力。

分割什么

react渲染大抵可以分为 reconciler(调度阶段)和 commit(渲染阶段),前者用于对比,后者用于操作dom,reconciler阶段可以算是一个从顶向下的递归算法,主要工作是对current treenew tree做计算,找出变化部分。commit阶段是对在reconciler阶段获取到的变化部分应用到真实的DOM树中,在绝大部分运用场景中,reconciler阶段的时间远远超过commit,因此fiber选择将reconciler阶段进行分割。

分割成什么

Fiber的拆分单位是fiber(fiber tree上的一个节点),这里引入了fiber tree的概念,fiber tree实际上是个单链表树结构,由fiber构成,fiber tree是根据和之前的virtual dom tree的树结构一模一样,只是节点携带的信息有差异。

export type Fiber = {   return: Fiber|null, // 父节点   child: Fiber|null, // 子节点   sibling: Fiber|null, // 兄弟节点   alternate: Fiber|null, //diff后差异信息   .....};复制代码

完整树结构可以见

fiber树

 

而这就是Fiber解决问题的思路,把之前无法中断的diff递归(持续占据主线程)拆分成一系列小任务,每次检查树上的一小部分,由于树上挂载了更多的上下文信息,因此可以选择是否继续下一个任务(时间是否允许当前帧(16ms)),而上面提到的alternate,则是一开始指向一个fiber tree的clone对象,每次任务完成会先将变化更新到这个克隆体上,等到diff结束,在进行commit。

 

workInProgress tree是什么?

export function createWorkInProgress(  current: Fiber,  pendingProps: any,  expirationTime: ExpirationTime,): Fiber {  let workInProgress = current.alternate;  if (workInProgress === null) {    workInProgress = createFiber(      current.tag,      pendingProps,      current.key,      current.mode,    );    workInProgress.type = current.type;    workInProgress.stateNode = current.stateNode;    if (__DEV__) {      // DEV-only fields      workInProgress._debugID = current._debugID;      workInProgress._debugSource = current._debugSource;      workInProgress._debugOwner = current._debugOwner;    }    workInProgress.alternate = current;    current.alternate = workInProgress;  } else {    workInProgress.pendingProps = pendingProps;    workInProgress.effectTag = NoEffect;    workInProgress.nextEffect = null;    workInProgress.firstEffect = null;    workInProgress.lastEffect = null;  }  workInProgress.expirationTime = expirationTime;  workInProgress.child = current.child;  workInProgress.memoizedProps = current.memoizedProps;  workInProgress.memoizedState = current.memoizedState;  workInProgress.updateQueue = current.updateQueue;  workInProgress.sibling = current.sibling;  workInProgress.index = current.index;  workInProgress.ref = current.ref;  return workInProgress;}}复制代码

我们通过源码可以看见,这其实就是之前我们提到的那个克隆体,workInProgress tree上每个节点都有一个effect list,用来存放需要更新的内容。而它的alternate则指向当前的fiber节点,这一点很关键,它可以复用之前的fiber树,下一次更新时不用重新创建fiber树,而可以更新fiber树,然后对应到相应的workInProgress tree上,完成了缓存的工作。

任务的优先级由什么决定

浏览器提供的requestIdleCallback API中的Cooperative Scheduling可以让浏览器在空闲时间执行回调(开发者传入的方法),在回调参数中可以获取到当前帧剩余的时间。利用这个信息可以合理的安排当前帧需要做的事情,如果时间足够,那继续做下一个任务,如果时间不够就歇一歇,调用requestIdleCallback来获知主线程不忙的时候,再继续做任务。

源码中的computeExpirationForFiber函数,该方法用于计算fiber更新任务的最晚执行时间,进行比较后,决定是否继续做下一个任务。

function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {  let expirationTime;  if (expirationContext !== NoWork) {    // An explicit expiration context was set;    expirationTime = expirationContext;  } else if (isWorking) {    if (isCommitting) {      // Updates that occur during the commit phase should have sync priority      // by default.      expirationTime = Sync;    } else {      // Updates during the render phase should expire at the same time as      // the work that is being rendered.      expirationTime = nextRenderExpirationTime;    }  } else {    // No explicit expiration context was set, and we're not currently    // performing work. Calculate a new expiration time.    if (fiber.mode & ConcurrentMode) {      if (isBatchingInteractiveUpdates) {        // This is an interactive update        expirationTime = computeInteractiveExpiration(currentTime);      } else {        // This is an async update        expirationTime = computeAsyncExpiration(currentTime);      }      // If we're in the middle of rendering a tree, do not update at the same      // expiration time that is already rendering.      if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {        expirationTime -= 1;      }    } else {      // This is a sync update      expirationTime = Sync;    }  }  if (isBatchingInteractiveUpdates) {    // This is an interactive update. Keep track of the lowest pending    // interactive expiration time. This allows us to synchronously flush    // all interactive updates when needed.    if (      lowestPriorityPendingInteractiveExpirationTime === NoWork ||      expirationTime < lowestPriorityPendingInteractiveExpirationTime    ) {      lowestPriorityPendingInteractiveExpirationTime = expirationTime;    }  }  return expirationTime;}复制代码

源码中的中描述了任务的优先级,用ExpirationTime的到期时间方式表示任务的优先级。

export type ExpirationTime = number;export const NoWork = 0;export const Never = 1;export const Sync = MAX_SIGNED_31_BIT_INT;const UNIT_SIZE = 10;const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;// 1 unit of expiration time represents 10ms.export function msToExpirationTime(ms: number): ExpirationTime {  // Always add an offset so that we don't clash with the magic number for NoWork.  return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);}复制代码

取代了15版本对优先级的简单划分

module.exports = {  NoWork: 0, // No work is pending.  SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.  AnimationPriority: 2, // Needs to complete before the next frame.  HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.  LowPriority: 4, // Data fetching, or result from updating stores.  OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.};复制代码

更新队列(UpdateQueue)

我们知道如果需要实现组件的异步更新,肯定需要在更新前将更新任务进行存储,然后异步任务开始的时候读取更新并实现组件更新,存储更新任务就需要一个数据结构,最常见的就是栈和队列,Fiber的实现方式就是队列。

这一部分内容在源码有详细描述,感兴趣的可以继续研究下去,包括如何根据优先级插入和更新队列


对fiber源码的研究先告一段落了,之后有时间会继续研究实现原理,也会更新在下方。你的下一句话是?

在这里插入图片描述

 

 

参考

1.

2.

作者:TimCope
链接:https://juejin.im/post/5da92a5af265da5b7415124a
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的文章
如何在vue中判断用户是否登录,登录权限
查看>>
Vue.js面试题整理
查看>>
Vuex入门
查看>>
详解Vue路由钩子及应用场景
查看>>
使用Webpack的代码分离实现Vue懒加载(译文)
查看>>
H5获取用户位置API + 百度地图API介绍
查看>>
Vue—知识点整理
查看>>
前端面经-webpack
查看>>
webpack面试
查看>>
浅析Vue源码
查看>>
Vue 数据响应式原理
查看>>
JavaScript 之实现一个简单的 Vue
查看>>
Vue知识点整理(面试)
查看>>
带你用vue撸后台 系列
查看>>
带你用合理的姿势使用webpack4(上)
查看>>
带你用合理的姿势使用webpack4(下)
查看>>
用 Vue 编写一个长按指令
查看>>
你需要 Mobx 还是 Redux ?
查看>>
一篇文章理解Web缓存
查看>>
Node.js 框架对比之 Express VS Koa
查看>>