成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

React源碼分析與實(shí)現(xiàn)(二):狀態(tài)、屬性更新 -> setState

BlackFlagBin / 1016人閱讀

摘要:流程圖大概如下的源碼比較簡(jiǎn)單,而在執(zhí)行更新的過(guò)程比較復(fù)雜。這段代碼的核心就是調(diào)用,然后對(duì)老的屬性和狀態(tài)存一下,新的更新一下而已。最后是把執(zhí)行推入的隊(duì)列中,等待組件的更新。最后調(diào)用統(tǒng)一修改的屬性。

原文鏈接地址:https://github.com/Nealyang%EF%BC%9A%E7%8A%B6%E6%80%81%E3%80%81%E5%B1%9E%E6%80%A7%E6%9B%B4%E6%96%B0%20-%3E%20setState.md) 轉(zhuǎn)載請(qǐng)注明出處
狀態(tài)更新
此次分析setState基于0.3版本,實(shí)現(xiàn)比較簡(jiǎn)單,后續(xù)會(huì)再分析目前使用的版本以及事務(wù)機(jī)制。

流程圖大概如下

setState的源碼比較簡(jiǎn)單,而在執(zhí)行更新的過(guò)程比較復(fù)雜。我們直接跟著源碼一點(diǎn)一點(diǎn)屢清楚。

ReactCompositeComponent.js

  /**
   * Sets a subset of the state. Always use this or `replaceState` to mutate
   * state. You should treat `this.state` as immutable.
   *
   * There is no guarantee that `this.state` will be immediately updated, so
   * accessing `this.state` after calling this method may return the old value.
   *
   * @param {object} partialState Next partial state to be merged with state.
   * @final
   * @protected
   */
  setState: function(partialState) {
    // Merge with `_pendingState` if it exists, otherwise with existing state.
    this.replaceState(merge(this._pendingState || this.state, partialState));
  },

注釋部分說(shuō)的很明確,setState后我們不能夠立即拿到我們?cè)O(shè)置的值。

而這段代碼也非常簡(jiǎn)單,就是將我們傳入的state和this._pendingState做一次merge,merge的代碼在util.js下

var merge = function(one, two) {
  var result = {};
  mergeInto(result, one);
  mergeInto(result, two);
  return result;
};

function mergeInto(one, two) {
  checkMergeObjectArg(one);
  if (two != null) {
    checkMergeObjectArg(two);
    for (var key in two) {
      if (!two.hasOwnProperty(key)) {
        continue;
      }
      one[key] = two[key];
    }
  }
}

  checkMergeObjectArgs: function(one, two) {
    mergeHelpers.checkMergeObjectArg(one);
    mergeHelpers.checkMergeObjectArg(two);
  },

  /**
   * @param {*} arg
   */
  checkMergeObjectArg: function(arg) {
    throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
  },
  
  var isTerminal = function(o) {
  return typeof o !== "object" || o === null;
};

var throwIf = function(condition, err) {
  if (condition) {
    throw new Error(err);
  }
};

診斷代碼的邏輯非常簡(jiǎn)單,其實(shí)功能就是Object.assign() ,但是從上面代碼我們可以看出react源碼中的function大多都具有小而巧的特點(diǎn)。

最終,將merge后的結(jié)果傳遞給replaceState

replaceState: function(completeState) {
    var compositeLifeCycleState = this._compositeLifeCycleState;
    invariant(
      this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
      compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
      "replaceState(...): Can only update a mounted (or mounting) component."
    );
    invariant(
      compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
      compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
      "replaceState(...): Cannot update while unmounting component or during " +
      "an existing state transition (such as within `render`)."
    );

    this._pendingState = completeState;

    // Do not trigger a state transition if we are in the middle of mounting or
    // receiving props because both of those will already be doing this.
    if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
        compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
      this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;

      var nextState = this._pendingState;
      this._pendingState = null;

      var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
      transaction.perform(
        this._receivePropsAndState,
        this,
        this.props,
        nextState,
        transaction
      );
      ReactComponent.ReactReconcileTransaction.release(transaction);

      this._compositeLifeCycleState = null;
    }
  },

撇開(kāi)50% 判斷warning代碼不說(shuō),從上面代碼我們可以看出,只有在componsiteLifeState不等于mounting和receiving_props 時(shí),才會(huì)調(diào)用 _receivePropsAndState函數(shù)來(lái)更新組件。

我們可以演示下:

var ExampleApplication = React.createClass({
      getInitialState() {
        return {}
      },
      componentWillMount() {
        this.setState({
          a: 1,
        })
        console.log("componentWillMount", this.state.a)
        this.setState({
          a: 2,
        })
        console.log("componentWillMount", this.state.a)
        this.setState({
          a: 3,
        })
        console.log("componentWillMount", this.state.a)
        setTimeout(() => console.log("a5"), 0)
        setTimeout(() => console.log(this.state.a,"componentWillMount"))

        Promise.resolve("a4").then(console.log)
      },

      componentDidMount() {
        this.setState({
          a: 4,
        })
        console.log("componentDidMount", this.state.a)
        this.setState({
          a: 5,
        })
        console.log("componentDidMount", this.state.a)
        this.setState({
          a: 6,
        })
        console.log("componentDidMount", this.state.a)
      },
      render: function () {
        var elapsed = Math.round(this.props.elapsed / 100);
        var seconds = elapsed / 10 + (elapsed % 10 ? "" : ".0");
        var message =
          "React has been successfully running for " + seconds + " seconds.";
        return React.DOM.p(null, message);
      }
    });

所以以上結(jié)果我們可以看出,在componentWillMount生命周期內(nèi)setState后this.state不會(huì)改變,在componentDidMount是正常的。因?yàn)樵谏弦黄恼轮形覀円灿姓f(shuō)到,在mountComponent過(guò)程中,會(huì)把compositeLifeCycleState設(shè)置為MOUNTING狀態(tài),在這個(gè)過(guò)程中,是不會(huì)執(zhí)行receivePropsAndState的,所以this.state也就不會(huì)更新,同理,在receivePropsAndState的過(guò)程中,會(huì)把compositeLifeCycleState置成RECEIVING_PROPS狀態(tài),也不會(huì)執(zhí)行state更新以及render執(zhí)行,在updateComponent過(guò)程中又執(zhí)行了mountComponent函數(shù),mountComponent函數(shù)調(diào)用了render函數(shù)。

而在現(xiàn)在我們使用16或者15版本中,我們發(fā)現(xiàn):

componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

最后打印的結(jié)果為:0,0,2,3

為什么有這樣呢?其實(shí)源于源碼中的這段代碼:

function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent"s
  // _renderValidatedComponent) assume that calls to render aren"t nested;
  // verify that that"s the case. (This is called by each top-level update
  // function, like setProps, setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
}

因?yàn)檫@里涉及到事務(wù)的概念、批量更新以及benchUpdate等,在我們目前分析的版本中還未迭代上去,后面我們會(huì)跟著版本升級(jí)慢慢說(shuō)道。

屬性更新

首先我們知道,屬性的更新必然是由于state的更新,所以其實(shí)組件屬性的更新流程就是setState執(zhí)行更新的延續(xù),換句話說(shuō),也就是setState才能出發(fā)組件屬性的更新,源碼里就是我在處理state更新的時(shí)候,順帶檢測(cè)了屬性的更新。所以這段源碼的開(kāi)始,還是從setState中看

  _receivePropsAndState: function(nextProps, nextState, transaction) {
    if (!this.shouldComponentUpdate ||
        this.shouldComponentUpdate(nextProps, nextState)) {
      this._performComponentUpdate(nextProps, nextState, transaction);
    } else {
      this.props = nextProps;
      this.state = nextState;
    }
  },

代碼非常的簡(jiǎn)單,一句話解釋:當(dāng)shouldComponentUpdate為true時(shí),則執(zhí)行更新操作。

  _performComponentUpdate: function(nextProps, nextState, transaction) {
    var prevProps = this.props;
    var prevState = this.state;

    if (this.componentWillUpdate) {
      this.componentWillUpdate(nextProps, nextState, transaction);
    }

    this.props = nextProps;
    this.state = nextState;

    this.updateComponent(transaction);

    if (this.componentDidUpdate) {
      transaction.getReactOnDOMReady().enqueue(
        this,
        this.componentDidUpdate.bind(this, prevProps, prevState)
      );
    }
  },

這段代碼的核心就是調(diào)用this.updateComponent,然后對(duì)老的屬性和狀態(tài)存一下,新的更新一下而已。如果存在componentWillUpdate就執(zhí)行一下,然后走更新流程。最后是把執(zhí)行componentDidUpdate推入getReactOnDOMReady的隊(duì)列中,等待組件的更新。

  _renderValidatedComponent: function() {
    ReactCurrentOwner.current = this;
    var renderedComponent = this.render();
    ReactCurrentOwner.current = null;
    return renderedComponent;
  },
  ...
  ...
  updateComponent: function(transaction) {
    var currentComponent = this._renderedComponent;
    var nextComponent = this._renderValidatedComponent();
    if (currentComponent.constructor === nextComponent.constructor) {
      if (!nextComponent.props.isStatic) {
        currentComponent.receiveProps(nextComponent.props, transaction);
      }
    } else {
      var thisID = this._rootNodeID;
      var currentComponentID = currentComponent._rootNodeID;
      currentComponent.unmountComponent();
      var nextMarkup = nextComponent.mountComponent(thisID, transaction);
      ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
        currentComponentID,
        nextMarkup
      );
      this._renderedComponent = nextComponent;
    }
  },

這里我們直接看updateComponent更新流程,首先獲取當(dāng)前render函數(shù)的組件,然后獲取下一次render函數(shù)的組件,_renderValidatedComponent就是獲取下一次的render組件。 通過(guò)Constructor來(lái)判斷組件是否相同,如果相同且組件為非靜態(tài),則更新組件的屬性,否則卸載當(dāng)前組件,然后重新mount下一個(gè)render組件并且直接暴力更新。

接著會(huì)調(diào)用render組件的receiveProps方法,其實(shí)一開(kāi)始這個(gè)地方我也是非常困惑的,this指向傻傻分不清楚,后來(lái)經(jīng)過(guò)各種查閱資料知道,它其實(shí)是一個(gè)多態(tài)方法,如果是復(fù)合組件,則執(zhí)行ReactCompositeComponent.receiveProps,如果是原生組件,則執(zhí)行ReactNativeComponent.receiveProps。源碼分別如下:

  receiveProps: function(nextProps, transaction) {
    if (this.constructor.propDeclarations) {
      this._assertValidProps(nextProps);
    }
    ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);

    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
    if (this.componentWillReceiveProps) {
      this.componentWillReceiveProps(nextProps, transaction);
    }
    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
    var nextState = this._pendingState || this.state;
    this._pendingState = null;
    this._receivePropsAndState(nextProps, nextState, transaction);
    this._compositeLifeCycleState = null;
  },

有人可能注意到這里的this._receivePropsAndState函數(shù),這不是剛才調(diào)用過(guò)么?怎么又調(diào)用一遍?沒(méi)錯(cuò),調(diào)用這個(gè)的this已經(jīng)是currentComponent了,并不是上一個(gè)this。currentComponent是當(dāng)前組件的render組件,也就是當(dāng)前組件的子組件。子組件同樣也可能是復(fù)合組件或者原生組件。正式通過(guò)這種多態(tài)的方式,遞歸的解析每級(jí)嵌套組件。最終完成從當(dāng)前組件到下面的所有葉子節(jié)點(diǎn)的樹(shù)更新。

其實(shí)話說(shuō)回來(lái),compositeComponent最終還是會(huì)遍歷遞歸到解析原生組件,通過(guò)我們整體瀏覽下ReactNativeComponent.js代碼可以看出。

我們先從 receiveProps方法開(kāi)始看

  receiveProps: function(nextProps, transaction) {
    assertValidProps(nextProps);
    ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
    this._updateDOMProperties(nextProps);
    this._updateDOMChildren(nextProps, transaction);
    this.props = nextProps;
  },
  
  function assertValidProps(props) {
  if (!props) {
    return;
  }
  var hasChildren = props.children != null ? 1 : 0;
  var hasContent = props.content != null ? 1 : 0;
  var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
}

刪除安全警告和注釋其實(shí)代碼非常簡(jiǎn)答,首先assertValidProps就是校驗(yàn)props是否合法的,更新屬性的方法就是_updateDOMProperties

_updateDOMProperties: function(nextProps) {
    var lastProps = this.props;
    for (var propKey in nextProps) {
      var nextProp = nextProps[propKey];
      var lastProp = lastProps[propKey];
      //判斷新老屬性中的值是否相等
      if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
        continue;
      }
      //如果是style樣式,遍歷新style,如果去舊style不相同,則把變化的存入styleUpdates對(duì)象中。最后調(diào)用 updateStylesByID 統(tǒng)一修改dom的style屬性。
      if (propKey === STYLE) {
        if (nextProp) {
          nextProp = nextProps.style = merge(nextProp);
        }
        var styleUpdates;
        for (var styleName in nextProp) {
          if (!nextProp.hasOwnProperty(styleName)) {
            continue;
          }
          if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = nextProp[styleName];
          }
        }
        if (styleUpdates) {
          ReactComponent.DOMIDOperations.updateStylesByID(
            this._rootNodeID,
            styleUpdates
          );
        }
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        var lastHtml = lastProp && lastProp.__html;
        var nextHtml = nextProp && nextProp.__html;
        if (lastHtml !== nextHtml) {
          ReactComponent.DOMIDOperations.updateInnerHTMLByID(//注意這里是innerHtml,所以dangerouslyInnerHTML會(huì)展示正常的HTML
            this._rootNodeID,
            nextProp
          );
        }
      } else if (propKey === CONTENT) {
        ReactComponent.DOMIDOperations.updateTextContentByID(//這里是innerText,所以content與children原封不動(dòng)的把HTML代碼打印到頁(yè)面上
          this._rootNodeID,
          "" + nextProp
        );
      } else if (registrationNames[propKey]) {
        putListener(this._rootNodeID, propKey, nextProp);
      } else {
        ReactComponent.DOMIDOperations.updatePropertyByID(
          this._rootNodeID,
          propKey,
          nextProp
        );
      }
    }
  },

這里面方法沒(méi)有太多的hack技巧,非常的簡(jiǎn)單直白,不多帶帶擰出來(lái)說(shuō),我直接寫到注釋里面了。

最后直接更新組件的屬性

  setValueForProperty: function(node, name, value) {
    if (DOMProperty.isStandardName[name]) {
      var mutationMethod = DOMProperty.getMutationMethod[name];
      if (mutationMethod) {
        mutationMethod(node, value);
      } else if (DOMProperty.mustUseAttribute[name]) {
        if (DOMProperty.hasBooleanValue[name] && !value) {
          node.removeAttribute(DOMProperty.getAttributeName[name]);
        } else {
          node.setAttribute(DOMProperty.getAttributeName[name], value);
        }
      } else {
        var propName = DOMProperty.getPropertyName[name];
        if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
          node[propName] = value;
        }
      }
    } else if (DOMProperty.isCustomAttribute(name)) {
      node.setAttribute(name, value);
    }
  }

整體屬性更新的流程圖大概如下:

結(jié)束語(yǔ)

通篇讀完,是不是有種

react源碼中包含很多的點(diǎn)的知識(shí),比如我們之前說(shuō)的VDOM、包括后面要去學(xué)習(xí)dom-diff、事務(wù)、緩存等等,都是一個(gè)點(diǎn),而但從一個(gè)點(diǎn)來(lái)切入難免有的會(huì)有些枯燥沒(méi)卵用,別急別急~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/97122.html

相關(guān)文章

  • 如何用ahooks控制時(shí)機(jī)的hook?

      本篇主要和大家溝通關(guān)于ahooks ,我們可以理解為加深對(duì) React hooks 的了解?! ∥覀兿日f(shuō)下關(guān)于抽象自定義 hooks。構(gòu)建屬于自己的 React hooks 工具庫(kù)。  其實(shí)我們應(yīng)該培養(yǎng)閱讀學(xué)習(xí)源碼的習(xí)慣,工具庫(kù)是一個(gè)對(duì)源碼閱讀不錯(cuò)的選擇?! ∽ⅲ罕鞠盗袑?duì) ahooks 的源碼解析是基于v3.3.13?! ‖F(xiàn)在就進(jìn)入主題用ahooks 來(lái)封裝 React要注意的時(shí)機(jī)?  Fun...

    3403771864 評(píng)論0 收藏0
  • React源碼解析之React.Component()/PureComponent()

    摘要:只涉及了,其他均沒(méi)有自己實(shí)現(xiàn)。這種組件的復(fù)用性是最強(qiáng)的。所以會(huì)新建,只繼承的原型,不包括,以此來(lái)節(jié)省內(nèi)存。 showImg(https://segmentfault.com/img/remote/1460000019783989); 一、React.Component() GitHub:https://github.com/AttackXiaoJinJin/reactExplain/...

    Cristalven 評(píng)論0 收藏0
  • 精讀《React PowerPlug 源碼

    摘要:今天我們就來(lái)解讀一下的源碼。比較有意思,將定時(shí)器以方式提供出來(lái),并且提供了方法。實(shí)現(xiàn)方式是,在組件內(nèi)部維護(hù)一個(gè)定時(shí)器,實(shí)現(xiàn)了組件更新銷毀時(shí)的計(jì)時(shí)器更新銷毀操作,可以認(rèn)為這種定時(shí)器的生命周期綁定了組件的生命周期,不用擔(dān)心銷毀和更新的問(wèn)題。 1. 引言 React PowerPlug 是利用 render props 進(jìn)行更好狀態(tài)管理的工具庫(kù)。 React 項(xiàng)目中,一般一個(gè)文件就是一個(gè)類,...

    teren 評(píng)論0 收藏0
  • React 源碼深度解讀(九):?jiǎn)蝹€(gè)元素更新

    摘要:作為聲明式的框架,接管了所有頁(yè)面更新相關(guān)的操作。是用于內(nèi)部操作的實(shí)例,這里將它的初始化為空數(shù)組并插入一個(gè)新的。連續(xù)次后,期望的結(jié)果應(yīng)該是。原因很簡(jiǎn)單,因?yàn)榇蔚臅r(shí)候,取到的都是在完后不會(huì)同步更新。 前言 React 是一個(gè)十分龐大的庫(kù),由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非常艱辛的過(guò)程...

    kid143 評(píng)論0 收藏0
  • 前端路由實(shí)現(xiàn) react-router 源碼分析

    摘要:回調(diào)函數(shù)將在更新時(shí)觸發(fā),回調(diào)中的起到了新的的作用。注冊(cè)回調(diào)在中使用注冊(cè)的回調(diào)函數(shù),最終放在模塊的回調(diào)函數(shù)數(shù)組中。 原文地址:https://github.com/joeyguo/blog/issues/2 在單頁(yè)應(yīng)用上,前端路由并不陌生。很多前端框架也會(huì)有獨(dú)立開(kāi)發(fā)或推薦配套使用的路由系統(tǒng)。那么,當(dāng)我們?cè)谡勄岸寺酚傻臅r(shí)候,還可以談些什么?本文將簡(jiǎn)要分析并實(shí)現(xiàn)一個(gè)的前端路由,并對(duì) reac...

    ISherry 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<