学react哪家强,阿宽带你看React的生命周期

栏目: 服务器 · 发布时间: 6年前

内容简介:其实,我也不懂,关于源码,我看了两次,只看懂了40%,丢脸了,但是还是硬着头皮,写篇文章,mark一下吧,源码来自 《深入浅出React技术》这本书....我只是大自然的搬运工学习一个框架,最重要的莫过于对生命周期的理解了。嗯,很懵,但是人傻就是要多看书,多看掘金上的优秀文章,看了两篇React生命周期的文章之后,大概也能懂得个大概。就记录一下吧 ~《深入浅出React技术》中对生命周期的说明:

其实,我也不懂,关于源码,我看了两次,只看懂了40%,丢脸了,但是还是硬着头皮,写篇文章,mark一下吧,源码来自 《深入浅出React技术》这本书....我只是大自然的搬运工

React 生命周期

学习一个框架,最重要的莫过于对生命周期的理解了。嗯,很懵,但是人傻就是要多看书,多看掘金上的优秀文章,看了两篇React生命周期的文章之后,大概也能懂得个大概。就记录一下吧 ~

先看图,再看字

《深入浅出React技术》中对生命周期的说明:

学react哪家强,阿宽带你看React的生命周期

渲染的过程:

学react哪家强,阿宽带你看React的生命周期

上图中的getDefaultProps和getInitialState分别对应ES6中的static defaultProps = {}与构造函数construct中的this.state ={}赋值

学react哪家强,阿宽带你看React的生命周期

生命周期 - 初次渲染

一个初始化组件 (以ES6 classes为例子)

    // 当使用 ES6 classes 编写 React 组件时,其实就是调用内部方法 createClass 创建组件

    import React, { Component } from 'react'

    class Index extends Component {

      static propTypes = {
        // code...
      }

      static defaultProps = {
        // code...
      }

      constructor(props) {
        super(props)
        this.state = {
          // code...
        }
      }

      componentWillMount () {
        // code...
      }

      componentDidMount () {
        
      }

      render () {
        return (
          // code...
        )
      }
    }
复制代码

我们来看看《深入React技术》中如何解读源码

var React = {
    // ...
    createClass: ReactClass.createClass,
    // ...
  }

  var ReactClass = {
    createClass: function(spec) {
      var Constructor = function(props, context, updater) {
        // 自动绑定
        if (this.__reactAutoBindPairs.length) {
          bindAutoBindMethods(this);
        }
        this.props = props;
        this.context = context;
        this.refs = emptyObject;
        this.updater = updater || ReactNoopUpdateQueue;
        this.state = null;
        // ReactClass 没有构造函数,通过 getInitialState 和 componentWillMount 来代替
        var initialState = this.getInitialState ? this.getInitialState() : null;
        this.state = initialState;
      }

      // 原型继承父类
      Constructor.prototype = new ReactClassComponent();
      Constructor.prototype.constructor = Constructor;
      Constructor.prototype.__reactAutoBindPairs = [];

      // 合并 mixin
      injectedMixins.forEach(
        mixSpecIntoComponent.bind(null, Constructor)
      );
      mixSpecIntoComponent(Constructor, spec);

      // 所有 mixin 合并后初始化 defaultProps(在整个生命周期中,getDefaultProps 只执行一次)
      if (Constructor.getDefaultProps) {
        Constructor.defaultProps = Constructor.getDefaultProps();
      }

      // 减少查找并设置原型的时间
      for (var methodName in ReactClassInterface) {
        if (!Constructor.prototype[methodName]) {
          Constructor.prototype[methodName] = null;
        }
      }

      return Constructor;
    }
  }



  // React Constructor 说明

  React规定constructor有三个参数,分别是props、context和updater。

  · props是属性,它是不可变的。
  
  · context是全局上下文。
  
  · updater是包含一些更新方法的对象

  // this.setState最终调用的是this.updater.enqueueSetState方法
  
  // this.forceUpdate最终调用的是this.updater.enqueueForceUpdate方法

复制代码
学react哪家强,阿宽带你看React的生命周期

mountComponent 组件挂载代码

// 当组件挂载时,会分配一个递增编号,表示执行 ReactUpdates 时更新组件的顺序
  var nextMountID = 1

  // 初始化组件,渲染标记,注册事件监听器
  mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) {
    // 当前元素对应的上下文
    this._context = context
    this._mountOrder = nextMountID
    this._nativeParent = nativeParent
    this._nativeContainerInfo = nativeContainerInfo

    var publicProps = this._processProps(this._currentElement.props)
    var publicContext = this._processContext(context)
    var Component = this._currentElement.type

    // 初始化公共类
    var inst = this._constructComponent(publicProps, publicContext)

    var renderedElement;

    // 判断组件是否为无状态组件,无状态组件没有状态更新队列,它只专注于渲染
    if (!shouldConstruct(Component) && (inst == null || inst.render == null)) {
      renderedElement = inst
      warnIfInvalidElement(Component, renderedElement)
      inst = new StatelessComponent(Component)
    }

    // 这些初始化参数本应该在构造函数中设置,在此设置是为了便于进行简单的类抽象 
    inst.props = publicProps
    inst.context = publicContext
    inst.refs = emptyObject
    inst.updater = ReactUpdateQueue

    this._instance = inst
    

    // 将实例存储为一个引用 
    ReactInstanceMap.set(inst, this)
    
    // 初始化 state
    var initialState = inst.state
    if (initialState === undefined) {
      inst.state = initialState = null
    }

    // 初始化更新队列 
    this._pendingStateQueue = null
    this._pendingReplaceState = false 
    this._pendingForceUpdate = false

    var markup;

    // 如果挂载出现错误
    if (inst.upstable_handleError) {
      markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent, nativeContainerInfo, transaction, context)
    } else {
      // 初始化挂载
      markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
context)
    }

    // 如果存在 componentDidMount , 则调用
    if (inst.componentDidMount) {
      transaction.getReactMountReady().enqueue(inst.componentDidMount, inst)
    }

    return markup
  }

  // 挂载错误处理
  performInitialMountWithErrorHandling: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
    var markup;
    var checkpoint = transaction.checkpoint()

    try {
      // 捕捉错误,没有错误则初始化挂载
      markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction,
context)
    } catch (e) {
      transaction.rollback(checkpoint)
      this._instance.unstable_handleError(e)
      if (this._pendingStateQueue) {
        this._instance.state = this._processPendingState(this._instance.props, this._instance.context)
      }

      checkpoint = transaction.checkpoint()
      // 如果捕捉到错误,则执行 unmountComponent 后,再初始化挂载
      this._renderedComponent.unmountComponent(true)
      
      transaction.rollback(checkpoint)

      markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context)

    }

    return markup

  }


  // 挂载组件
  performInitialMount: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
    var inst = this._instance
    
    // 如果存在 componentWillMount, 则调用
    if (inst.componentWillMount) {
      inst.componentWillMount()

      // componentWillMount 调用 setState 时,不会触发 re-render 而是自动提前合并

      if (this._pendingStateQueue) {
        inst.state = this._processPendingState(inst.props, inst.context)
      }
    }

    // 如果不是无状态组件,即可开始渲染
    if (renderedElement === undefined) {
      renderedElement = this._renderValidatedComponent()
    }

    this._renderedNodeType = ReactNodeTypes.getType(renderedElement)
     // 得到 _currentElement 对应的 component 类实例 
    this._renderedComponent = this._instantiateReactComponent(renderedElement)

    // render 递归渲染
    var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent,
nativeContainerInfo, this._processChildContext(context))

    return markup
  }
复制代码

总结一下 - 初次渲染 ?

1 . 当使用 ES6 classes 编写 React 组件时,其实就是调用内部方法 createClass 创建组件, 该方法返回一个Constructor(props, context, updater) 用来生成组件实例,我们发现在调用React.createClass,已经执行了getDefaultProps(),并将其赋值于Constructor的原型中

2 . 由于通过ReactCompositeComponentBase 返回的是一个虚拟节点,所以需要利用 instantiateReactComponent去得到实例,再使用 mountComponent 拿到结果作为当前自定义元素的结果

当使用 React 创建组件时,首先会调用 instantiateReactComponent,这是初始化组件的入口 函数,它通过判断 node 类型来区分不同组件的入口 (具体看下边说明)

3 . 在React中,因为所有class组件都要继承自Component类或者PureComponent类,因此和原生class写法一样,要在constructor里首先调用super方法,才能获得this。通过 mountComponent 挂载组件,初始化序号、标记等参数,判断是否为无状态组件,并进行 对应的组件初始化工作,比如初始化 props、context 等参数。利用 getInitialState 获取初始化 state、初始化更新队列和更新状态。

4 . 若存在 componentWillMount,则执行。如果此时在 componentWillMount 中调用 setState 方法,是不会触发 re-render的,而是会进行 state 合并,且 inst.state = this._processPendingState (inst.props, inst.context) 是在 componentWillMount 之后执行的,因此 componentWillMount 中 的 this.state 并不是最新的,在 render 中才可以获取更新后的 this.state。

React 是利用更新队列 this._pendingStateQueue 以及更新状态 this._pendingReplaceState 和 this._pendingForceUpdate 来实现 setState 的异步更新机制。也就是说 this.setState 最终调用的是this.updater.enqueueSetState方法

5 . 当渲染完成后,若存在 componentDidMount,则调用。其实,mountComponent 本质上是通过递归渲染内容的,由于递归的特性,父组件的 componentWillMount 在其子组件的 componentWillMount 之前调用,而父组件的 componentDidMount 在其子组件的 componentDidMount 之后调用。

学react哪家强,阿宽带你看React的生命周期

额外补充

instantiateReactComponent 入口组件

· 当 node 为空时,说明 node 不存在,则初始化空组件 ReactEmptyComponent.create(instantiateReactComponent)。

  · 当 node 类型为对象时,即是 DOM 标签组件或自定义组件,那么如果 element 类型为字符串时 ,则初始化 DOM 标签组件ReactNativeComponent.createInternalComponent (element),否则初始化自定义组件 ReactCompositeComponentWrapper()

  · 当 node 类型为字符串或数字时,则初始化文本组件 ReactNativeComponent.createInstanceForText(node)。

  · 如果是其他情况,则不作处理

  // instantiateReactComponent 方法源码, 初始化组件入口

  function instantiateReactComponent(node, parentCompositeType) {
    var instance;

    // 空组件 (ReactEmptyComponent)
    if (node === null || node === false) {
      instance = ReactEmptyComponent.create(instantiateReactComponent)
    }

    // 对象类型
    if (typeof node === 'object') {
      var element = node
      if (typeof element === 'string') {
        instance = ReactNativeComponent.createInternalComponent (element)
      } else if (isInternalComponentType(element.type)) {
          // 不是字符串表示的自定义组件暂无法使用,此处将不做组件初始化操作
          instance = new element.type(element)
      } else {
        // 自定义组件
        instance = new ReactCompositeComponentWrapper()
      }
    } else if (typeof node === 'string' || typeof node === 'number') {
      // 字符串或数字
      instance = ReactNativeComponent.createInstanceForText(node)
    } else {
      // 不做处理
    }

    // 设置实例
    instance.construct(node)
    // 初始化参数
    instance._mountIndex = 0
    instance._mountImage = null

    return instance
    
  }
  
复制代码

生命周期 - 更新阶段

学react哪家强,阿宽带你看React的生命周期

updateComponent 负责管理生命周期中的 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate

首先通过 updateComponent 更新组件,如果前后元素不一致,说明需要进行组件更新

如果存在 componentWillReceiveProps, 则执行。如果此时在 componentWillReceiveProps 中调 用 setState,是不会触发 re-render 的,而是会进行 state 合并。且在 componentWillReceiveProps、 shouldComponentUpdate 和 componentWillUpdate 中也还是无法获取到更新后的 this.state,即此 时访问的 this.state 仍然是未更新的数据,需要设置 inst.state = nextState 后才可以,因此 只有在 render 和 componentDidUpdate 中才能获取到更新后的 this.state。

调用 shouldComponentUpdate 判断是否需要进行组件更新,如果存在 componentWillUpdate, 则执行。

updateComponent 本质上也是通过递归渲染内容的,由于递归的特性,父组件的 componentWillUpdate 是在其子组件的 componentWillUpdate 之前调用的,而父组件的 componentDidUpdate 也是在其子组件的 componentDidUpdate 之后调用的。

当渲染完成之后,若存在 componentDidUpdate,则触发

相关源码

// receiveComponent 是通过调用 updateComponent 进行组件更新的
  receiveComponent: function(nextElement, transaction, nextContext) {
    var prevElement = this._currentElement; var prevContext = this._context;
    this._pendingElement = null;
    this.updateComponent(transaction, prevElement, nextElement, prevContext, nextContext);
  },

  updateComponent: function(transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
    var inst = this._instance
    var willReceive = false
    var nextContext
    var nextProps

    // 上下文是否改变
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context
    } else {
      nextContext = this._processContext(nextUnmaskedContext)
      willReceive = true 
    }

    if (preParentElement === nextParentElement) {
      // 元素相同,跳过元素类型检测
      nextProps = nextParentElement.props
    } else {
      // 检查元素的类型
      nextProps = this._processProps(nextParentElement.props)
      willReceive = true
    }

    // 如果存在 compnentWillReceiveProps ,则调用
    if (inst.componentWillReceiveProps && willReceive) {
      inst.componentWillReceiveProps(nextProps, nextContext)
    }

    // 将新的state合并到更新的队列中, 此时的 nextState 是最新的 state
    var nextState = this._processPendingState(nextProps, nextContext)

    // 根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件
    var shouldUpdate = this._pendingForceUpdate || !inst.shouldComponentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext)

    if (shouldUpdate) {
      // 重置更新队列
      this._pendingForceUpdate = false
      // 即将更新 this.props 、 this.state 、 this.context
      this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext)
    } else {
      // 如果确定组件不更新,那么仍然要设置 props 和 state
      this._currentElement = nextParentElement
      this._context = nextUnmaskedContext
      inst.props = nextProps
      inst.state = nextState
      inst.context = nextContext
    }
  },

  // 当确定组件需要更新时,则调用
  _performComponentUpdate: function(nextElement, nextProps, nextState, nextContext, transaction, unmaskedContext) {
    var inst = this._instance
    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate)

    var preProps
    var preState
    var preContext

    // 如果存在 componentDidUpdate , 则将当前的 state, props, context 保存一份
    if (hasComponentDidUpdate) {
      preProps = inst.props
      preState = inst.state
      preContext = inst.context
    }

    // 如果存在 componentWillUpdate ,则调用
    if (inst.componentWillUpdate) {
      inst.componetWillUpdate(nextProps, nextState, nextContext)
    }

    this._currentElement = nextParentElement
    this._context = unmaskedContext

    // 更新 this.props 、 this.state 、 this.context
    inst.props = nextProps
    inst.state = nextState
    inst.context = nextContext

    // 调用 render 渲染组件
    this._updateRenderedComponent(transaction, unmaskedContext)

    // 当组件完成更新后,如果存在 componentDidUpdate,则调用 
    if (hasComponentDidUpdate) {
      transaction.getReactMountReady().enqueue( 
        inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),inst
      ) 
    }

  }

  // 调用 render 渲染组件
  _updateRenderedComponent: function (transaction, context) {
    var preComponentInstance = this._renderedComponet
    var preRenderedElement = prevComponentInstance._currentElement
    var nextRenderedElement = this._renderValidatedComonet()

    // 如果需要更新,则调用ReactReconciler.receiveComponent 继续更新组件
    if (shouldUpdateReactComponent(preRenderedElement, nextRenderedElement)) {
      ReactReconciler.receiveComponent(preComponentInstance, nextRenderedElement, transaction, this._processChildContext(context)) 
    } else {
      // 如果不需要更新, 则渲染组件
      var oldNativeNode = ReactReconciler.getNativeNode(preComponentInstance)
      ReactReconciler.unmountComponent(preComponentInstance)

      this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement)

      // 得到 nextRenderedElement 对应的component 类实例

      this._renderedComponet = this._instantiateReactComponent(nextRenderedElement)

      // 使用 render 递归渲染
      var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent,transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context))

      tgus._replaceNodeWithMarkup(oldNativeNode, nextMarkup)
    }
  }
复制代码

setState 循环调用的风险

禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用this.setState,因为这样会造成循环调用,直到耗光浏览器内存后奔溃,那么为什么不能呢 ?

1 . 调用 setState 时,实际上会执行 enqueueSetState 方法,并对partialState和_pendingStateQueue更新队列进行合并操作,最终通过 enqueueUpdate 执行 state 的更新

2 . 而 performUpdateIfNecessary 方法会获取 _pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 receiveComponent 和 updateComponent 方法进行组件更新

3 . 如 果 在 shouldComponentUpdate 或 componentWillUpdate 方 法 中 调 用 setState , 此 时 this._pendingStateQueue != null,则 performUpdateIfNecessary 方法就会调用 updateComponent 方法进行组件更新,但 updateComponent 方法又会调用 shouldComponentUpdate 和 componentWill- Update 方法,因此造成循环调用,使得浏览器内存占满后崩溃

复制代码

生命周期 - 卸载阶段

unmountComponent 负责管理生命周期中的 componentWillUnmount

如果存在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如 果此时在 componentWillUnmount 中调用 setState,是不会触发 re-render 的,这是因为所有更新 队列和更新状态都被重置为 null,并清除了公共类,完成了组件卸载操作

相关源码

unmountComponent: function(safely) {
    if (!this._renderedComponent) {
      return
    }

    var inst = this._instance

    // 如果存在 componentWillUnmount, 则调用
    if (inst.componentWillUnmount) {
      if (safely) {
        var name = this.getName() + '.componentWillUnmount()'
        ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst))
      } else {
        inst.componentWillUnmount()
      }
    }

    // 如果组件已经渲染,则对组件进行 unmountComponent 操作
    if (this._renderedComponent) {
      ReactReconciler.unmountComponent(this._renderedComponent, safely)
      this._renderedNodeType = null
      this._renderedComponent = null
      this._instance = null
    }

    // 重置相关参数、更新队列以及更新状态 
    this._pendingStateQueue = null   // 更新队列
    this._pendingReplaceState = false // 更新状态
    this._pendingForceUpdate = false  
    this._pendingCallbacks = null
    this._pendingElement = null
    this._context = null
    this._rootNodeID = null
    this._topLevelWrapper = null
    // 清除公共类
    ReactInstanceMap.remove(inst)

  }

复制代码

在 React 开发中,一个很重要的原则就是让组件尽可能是无状态的,无状态组件没有状态,没有生命周期,只是简单地接受 props 渲染生成 DOM 结构,是一个 纯粹为渲染而生的组件。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python语言程序设计

Python语言程序设计

(美)Y. Daniel Liang / 机械工业出版社 / 2013-3 / 79.00元

本书保持了Liang博士系列丛书中一贯的、标志性的教与学的哲学:以实例教,由实践学。书中采用了他所提出的已经经过实践检验的“基础先行”的方法,即在定义类之前,首先使用清晰简明的语言介绍基本程序设计概念,如选择语句、循环和函数;在介绍面向对象程序设计和GUI编程之前,首先介绍基本逻辑和程序设计概念。书中除了给出一些以游戏和数学为主的典型实例外,还在每章的开始使用简单的图形给出一两个例子,以激发学生的......一起来看看 《Python语言程序设计》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具