学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 结构,是一个 纯粹为渲染而生的组件。


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

查看所有标签

猜你喜欢:

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

Inside Larry's and Sergey's Brain

Inside Larry's and Sergey's Brain

Richard Brandt / Portfolio / 17 Sep 2009 / USD 24.95

You’ve used their products. You’ve heard about their skyrocketing wealth and “don’t be evil” business motto. But how much do you really know about Google’s founders, Larry Page and Sergey Brin? Inside......一起来看看 《Inside Larry's and Sergey's Brain》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具