React 浅析

React 生命周期

主要有三个过程:装载(Mount)过程、更新(Update)过程、卸载(Unmount)过程

  • 首次装载组件时,执行 constructor、getDefaultProps、getInitialState、
    componentWillMount、render、componentDidMount

  • 卸载组件时,执行 componentWillUnmount

  • 重新装载组件时,getInitialState、componentWillMount、render、componentDidMount

  • 更新渲染组件时,
    Props更新,执行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate

State更新,执行 shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate

getDefaultProps 和 getInitialState 只有用 React.createClass 方法创造的组件类才会发生作用,getInitialState 在组件的整个生命周期中只会被调用一次

getDefaultProps 是通过 Constructor 进行管理,因此也是整个生命周期中最先开始执行。
getDefaultProps 方法只执行一次,这样所有实例初始化的 props 将会被共享。

render 函数是一个纯函数,并不做实际的渲染动作,只是返回一个 JSX 对象,最终由 React 操作渲染过程

setState 更新机制

当调用 setState 时,会对 state 以及 _pendingState 更新队列进行合并操作,但其实真正更新 state 的幕后黑手是replaceState。

replaceState 会先判断当前状态是否为 MOUNTING,如果不是即会调用 ReactUpdates.enqueueUpdate 执行更新。

当状态不为 MOUNTING 或 RECEIVING_PROPS 时,performUpdateIfNecessary 会获取 _pendingElement、_pendingState、_pendingForceUpdate,并调用 updateComponent 进行组件更新。

如果在 shouldComponentUpdate 或 componentWillUpdate 中调用 setState,此时的状态已经从 RECEIVING_PROPS -> NULL,则 performUpdateIfNecessary 就会调用 updateComponent 进行组件更新,但 updateComponent 又会调用 shouldComponentUpdate 和 componentWillUpdate,因此造成循环调用,使得浏览器内存占满后崩溃。

解密setState

调用 setState 方法更新 State 时,不会立即生效,会推入 pending 队列,然后判断当前处于的阶段是否在 batch Update,如果是,则将该组件推入 dirtyComponents 中。如果不是,则会遍历所有的 dirtyComponents,调用 updateComponent 更新 pending state or props

React Diff

React diff 会计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行实际 DOM 操作,而非重新渲染整个页面,从而保证了每次操作更新后页面的高效渲染。

Diff 策略

  • DOM 节点跨层级的移动操作极少
  • 拥有相同类型的两个组件将会生成相似的树形结构,拥有不同类型的两个组件将会生成不同的树形结构。
  • 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分。

基于以上三个策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化。

Tree Diff

React 对树进行分层比较,两棵树只会对同一层次的节点进行比较。

React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对同一层级的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

如果有跨层级的 DOM 节点操作,React 只会简单的考虑同层级节点的位置变换,而对于不同层级的节点,只有创建和删除操作。

Component Diff

  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree
  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。

Element Diff

当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。

新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置。

参考知乎专栏 pure render —— React源码剖析系列

坚持原创技术分享,您的支持将鼓励我继续创作!