Reactソースの分析と実現(二):状態、属性の更新->set State


原文のリンク先: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-%3 E%20 set Start.md)転載は出所を明記してください.
ステータスの更新
今回はset Stateを分析して0.3バージョンに基づいて、比較的簡単なことを実現して、後で現在使用しているバージョンと事務機構を分析します.
フローチャートは大体次の通りです.
set Stateのソースコードは比較的に簡単ですが、更新を実行する過程では複雑です.私たちは直接ソースに従って少しずつ分かります.
  • React Component.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));
      },
    コメントの部分は明確にしています.セットした値はすぐにもらえません.
    このコードは非常に簡単です.つまり、私たちが入ってきた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);
      }
    };
    診断コードのロジックはとても簡単です.機能はObject.assign()です.でも、上のコードからreactソースのfunctionの多くは小さくて巧妙な特徴を持っていることが分かります.
    最後に、merge後の結果を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;
        }
      },
    50%を切り离してwarningコードを判断します.上のコードから见れば、compnsite LifeStateだけがmountingとreceivingに等しくないです.propsの場合は、_を呼び出します.receive PropsAndState関数がコンポーネントを更新します.
    デモできます.
    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);
          }
        });
    したがって、以上の結果から、component WillMountライフサイクルでset State後のthis.stateは変更されません.component DidMountでは正常です.前の記事でも、mount Componentでは、compsiteLifeCycleStateをMOUNTING状態に設定していますが、この過程では、receive PropsAndStateは実行されませんので、this.IVstateも更新されません.PROPS状態では、state更新やrender実行も行われず、udateComponent中にmount Component関数を実行し、mount Component関数がrender関数を呼び出しました.
    16または15のバージョンを使用しています.
    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);
      }
    最後に印刷した結果は0,0,2,3です.
    なぜですか?ソースコードのこの部分から来ています.
    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);
    }
    ここでは事務の概念、一括更新、benchUpdateなどに関連していますので、現在分析されているバージョンの中ではまだ反復されていません.バージョンアップに従ってゆっくり話します.
    属性の更新
    まず、属性の更新は必ずstateの更新のためであることを知っています.だから、実はコンポーネント属性の更新プロセスはset Stateの更新の継続です.つまり、set Stateはコンポーネント属性の更新に出発できます.ソースは私がstateの更新を処理する時、ついでに属性の更新を検出しました.だから、このソースの始まりは、まだset Stateから見てもいいです.
      _receivePropsAndState: function(nextProps, nextState, transaction) {
        if (!this.shouldComponentUpdate ||
            this.shouldComponentUpdate(nextProps, nextState)) {
          this._performComponentUpdate(nextProps, nextState, transaction);
        } else {
          this.props = nextProps;
          this.state = nextState;
        }
      },
    コードは非常に簡単で、一言で説明します.ショルドComponentUpdateがtrueである場合、更新操作を行います.
      _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)
          );
        }
      },
    このコードの核心はthis.updateComponentを呼び出して、古い属性と状態を保存して、新しい更新をするだけです.componentWillUpdateがあれば実行して、更新プロセスを実行します.最後に、component DidUpdateをget React OnDOMREAdyのキューに押し込み、コンポーネントの更新を待つ.
      _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の更新フローを直接見て、まず現在のrender関数のコンポーネントを取得し、次にrender関数のコンポーネントを取得し、_renderValidatedComponentは次のrenderコンポーネントを取得することである.コンストラクタによってコンポーネントが同じかどうかを判断し、同じであり、またコンポーネントが非静的であれば、コンポーネントの属性を更新し、そうでなければ、現在のコンポーネントをアンインストールし、次にモジュールを再mountし、直接的に暴力的に更新する.
    続いてレンデコンポーネントのreceiveProps方法を呼び出します.実はこの場所は最初はとても困惑しました.thisは馬鹿を指して分かりませんでした.その後いろいろな資料を調べて、複合部品ならReactComponent.receive Propsを実行します.ソースは以下の通りです.
      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._.に気づく人がいるかもしれません.receive PropsAndState関数は、先ほど呼び出したのではないですか?どうやってまた呼び出しますか?そうです.これを呼び出すthisはすでにcurrentComponentです.前のthisではありません.currentComponentは現在のコンポーネントのrenderコンポーネント、つまり現在のコンポーネントのサブコンポーネントです.サブアセンブリもまた、複合コンポーネントまたは元のコンポーネントである可能性がある.このような多形の方法により、再帰的な解析は、各段階のネストされたコンポーネントを正式に行う.最終的には、現在のコンポーネントから下のすべての葉っぱノードのツリー更新が完了します.
    実は、compsiteComponentは最終的には分析のもとのコンポーネントに戻ってきます.全体を通して、React NativeComponent.jsコードをご覧ください.
    まずreceive Props方法から見ます.
      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;
    }
    セキュリティ警告とコメントを削除します.コードはとてもシンプルです.まず、astertValidPropsは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   。     updateStylesByID     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      HTML
                this._rootNodeID,
                nextProp
              );
            }
          } else if (propKey === CONTENT) {
            ReactComponent.DOMIDOperations.updateTextContentByID(//   innerText,  content children      HTML        
              this._rootNodeID,
              '' + nextProp
            );
          } else if (registrationNames[propKey]) {
            putListener(this._rootNodeID, propKey, nextProp);
          } else {
            ReactComponent.DOMIDOperations.updatePropertyByID(
              this._rootNodeID,
              propKey,
              nextProp
            );
          }
        }
      },
    この中の方法はあまり多くないhackテクニックです.とても簡単でまっすぐで、単独で絞り出さずに説明します.直接にコメントに書きます.
    最後にコンポーネントのプロパティを直接更新します.
      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);
        }
      }
    全体的な属性更新のフローチャートは以下の通りです.
    結尾語
    全部読みました.種類がありますか?
    reactソースの中には多くの点の知识が含まれています.例えば、私たちが前に言ったVDOM、后ろからdom-diff、事务、キャッシュなどを勉强しに行きます.全部同じ点ですが、一つの点から切り込むとどうしても一部の味気ない卵があります.焦らないでください.