React 源碼學習(八):組件更新

閱讀源碼成了今年的學習目標之一,在選擇 Vue 和 React 之間,我想先閱讀 React 。 在考慮到讀哪一個版本的時候,我想先接觸到源碼早期的思想可能會更輕鬆一些,最終我選擇閱讀 0.3-stable 。 那麼接下來,我將從幾個方面來解讀這個版本的源碼。javascript

  1. React 源碼學習(一):HTML 元素渲染
  2. React 源碼學習(二):HTML 子元素渲染
  3. React 源碼學習(三):CSS 樣式及 DOM 屬性
  4. React 源碼學習(四):事務機制
  5. React 源碼學習(五):事件機制
  6. React 源碼學習(六):組件渲染
  7. React 源碼學習(七):生命週期
  8. React 源碼學習(八):組件更新

是什麼引發組件更新

引起組件更新的方法就是 this.setState ,按照註釋代碼看來 this.setState 是不可變的,則 this._pendingState 是用來存放掛起的 state ,他不會直接更新到 this.state ,讓咱們來看到代碼:html

// core/ReactCompositeComponent.js
var ReactCompositeComponentMixin = {
  setState: function(partialState) {
    // 若是「掛起狀態」存在,則與之合併,不然與現有狀態合併。
    this.replaceState(merge(this._pendingState || this.state, partialState));
  },
  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;

    // 若是咱們處於安裝或接收道具的中間,請不要觸發狀態轉換,由於這二者都已經這樣作了。
    // 若複合組件生命週期不在掛載中和更新 props 時,咱們會操做更新方法
    if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
        compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
      // 變動複合組件生命週期爲更新 state
      this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;

      // 準備更新 state ,並釋放掛起狀態
      var nextState = this._pendingState;
      this._pendingState = null;

      // 進入 React 調度事務,加入 _receivePropsAndState 方法
      var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
      transaction.perform(
        this._receivePropsAndState,
        this,
        this.props,
        nextState,
        transaction
      );
      ReactComponent.ReactReconcileTransaction.release(transaction);

      // 調度事務完成後置空複合組件生命週期
      this._compositeLifeCycleState = null;
    }
  },
};
複製代碼

setState 觸發了什麼

那麼到此爲止,你能夠知道 this.setState 並不是事實更新 this.state 的,好比咱們看到在 componentWillMount 中去使用 this.setState 並不會立刻更新到 this.state ,那麼咱們繼續閱讀後面代碼:java

// core/ReactCompositeComponent.js
var ReactCompositeComponentMixin = {
  _receivePropsAndState: function(nextProps, nextState, transaction) {
    // shouldComponentUpdate 方法不存在或返回 true
    if (!this.shouldComponentUpdate ||
        this.shouldComponentUpdate(nextProps, nextState)) {
      // Will set `this.props` and `this.state`.
      this._performComponentUpdate(nextProps, nextState, transaction);
    } else {
      // 若是肯定某個組件不該該更新,咱們仍然須要設置props和state。
      // shouldComponentUpdate 返回 false 的狀況
      this.props = nextProps;
      this.state = nextState;
    }
  },
  _performComponentUpdate: function(nextProps, nextState, transaction) {
    // 存入舊的 props 和 state
    // 用於傳入 componentDidUpdate
    var prevProps = this.props;
    var prevState = this.state;

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

    // 更新 props 和 state
    this.props = nextProps;
    this.state = nextState;

    // 更新組件
    this.updateComponent(transaction);

    if (this.componentDidUpdate) {
      transaction.getReactOnDOMReady().enqueue(
        this,
        this.componentDidUpdate.bind(this, prevProps, prevState)
      );
    }
  },
  updateComponent: function(transaction) {
    // 這裏的更新比較硬核
    // 先把已渲染的舊的組件賦值至 currentComponent
    var currentComponent = this._renderedComponent;
    // 直接渲染新的組件(是否是很硬核)
    var nextComponent = this._renderValidatedComponent();
    // 若是是一樣的組件則進入此判斷
    // 經過 constructor 來判斷是否爲同一個
    // 好比:
    // React.DOM.a().constructor !== React.DOM.p().constructor
    // React.DOM.a().constructor === React.DOM.a().constructor
    // 或
    // React.createClass({ render: () => null }).constructor ===
    // React.createClass({ render: () => null }).constructor
    if (currentComponent.constructor === nextComponent.constructor) {
      // 若新的組件 props 下 isStatic 爲真則不更新
      // 知道這一個能夠對部分組件進行手動優化,以避免沒必要要的計算
      if (!nextComponent.props.isStatic) {
        // 這裏會調用對應的方法
        // ReactCompositeComponent.receiveProps
        // ReactNativeComponent.receiveProps
        // ReactTextComponent.receiveProps
        // 除了 ReactTextComponent 都會調用 ReactComponent.Mixin.receiveProps 來更新 ref 相關
        // 這個咱們稍後來解讀
        currentComponent.receiveProps(nextComponent.props, transaction);
      }
    } else {
      // These two IDs are actually the same! But nothing should rely on that.
      // 舊的 _rootNodeID 和新的 _rootNodeID
      var thisID = this._rootNodeID;
      var currentComponentID = currentComponent._rootNodeID;
      // 卸載舊組件
      currentComponent.unmountComponent();
      // 掛載新組件(也挺硬核的)
      var nextMarkup = nextComponent.mountComponent(thisID, transaction);
      // 在新 _rootNodeID 下更新 markup 標記
      ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
        currentComponentID,
        nextMarkup
      );
      // 賦值新的組件
      this._renderedComponent = nextComponent;
    }
  },
};
複製代碼

各個組件的 receiveProps 方法

上面代碼看來,一個是不替換組件的狀況下更新組件,另外一個則是直接更新 markup 標記。咱們按照順序一個個看過來吧,先看到 ReactCompositeComponent.receivePropsnode

// core/ReactCompositeComponent.js
var ReactCompositeComponentMixin = {
  receiveProps: function(nextProps, transaction) {
    // 校驗參數
    if (this.constructor.propDeclarations) {
      this._assertValidProps(nextProps);
    }
    // 更新 ref
    ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);

    // 更新複合組件生命週期爲更新 props
    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
    // 執行鉤子函數,在這個函數內執行 this.setState 是不會當即更新 this.state 的
    if (this.componentWillReceiveProps) {
      this.componentWillReceiveProps(nextProps, transaction);
    }
    // 進入複合組件生命週期更新 state
    this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
    // When receiving props, calls to `setState` by `componentWillReceiveProps`
    // will set `this._pendingState` without triggering a re-render.
    // 若是上面執行過 componentWillReceiveProps ,而且裏面操做了 this.setState
    // 那麼 this._pendingState 會有值,而且是與 this.state 合併過的
    var nextState = this._pendingState || this.state;
    // 釋放 this._pendingState
    this._pendingState = null;
    // 執行的是 currentComponent._receivePropsAndState 方法
    // 可是這個 currentComponent 必定是 ReactCompositeComponent
    this._receivePropsAndState(nextProps, nextState, transaction);
    // 置空複合組件生命週期
    this._compositeLifeCycleState = null;
  },
};
複製代碼

再是咱們來看看 ReactNativeComponent.receivePropsapp

// core/ReactNativeComponent.js
ReactNativeComponent.Mixin = {
  receiveProps: function(nextProps, transaction) {
    // 平常校驗
    invariant(
      this._rootNodeID,
      'Trying to control a native dom element without a backing id'
    );
    assertValidProps(nextProps);
    // 平常更新 ref
    ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
    // 重點來了,更新 DOM 屬性
    this._updateDOMProperties(nextProps);
    // 更新 DOM 子節點
    this._updateDOMChildren(nextProps, transaction);
    // 都執行完後更新 props
    this.props = nextProps;
  },
  _updateDOMProperties: function(nextProps) {
    // 這裏開始解讀更新 DOM 屬性
    // 保存舊 props
    var lastProps = this.props;
    // 遍歷新 props
    for (var propKey in nextProps) {
      var nextProp = nextProps[propKey];
      var lastProp = lastProps[propKey];
      // 以新 props 鍵爲準取對應的值
      // 若 2 個值相等則跳過
      if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
        continue;
      }
      // CSS 樣式
      if (propKey === STYLE) {
        if (nextProp) {
          nextProp = nextProps.style = merge(nextProp);
        }
        var styleUpdates;
        // 遍歷 nextProp
        for (var styleName in nextProp) {
          if (!nextProp.hasOwnProperty(styleName)) {
            continue;
          }
          // 舊的 styleName 與新的 styleName 值不一樣時
          // 將新的值加入 styleUpdates
          if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
            if (!styleUpdates) {
              styleUpdates = {};
            }
            styleUpdates[styleName] = nextProp[styleName];
          }
        }
        // 操做更新 CSS 樣式
        if (styleUpdates) {
          // ReactComponent.DOMIDOperations => ReactDOMIDOperations
          // 他會經過 ID 對真實 node 進行相應的更新
          ReactComponent.DOMIDOperations.updateStylesByID(
            this._rootNodeID,
            styleUpdates
          );
        }
        // 判斷如果 dangerouslySetInnerHTML 則在不一樣的狀況下進行相應的更新
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        var lastHtml = lastProp && lastProp.__html;
        var nextHtml = nextProp && nextProp.__html;
        if (lastHtml !== nextHtml) {
          ReactComponent.DOMIDOperations.updateInnerHTMLByID(
            this._rootNodeID,
            nextProp
          );
        }
        // 判斷 content 的狀況更新
      } else if (propKey === CONTENT) {
        ReactComponent.DOMIDOperations.updateTextContentByID(
          this._rootNodeID,
          '' + nextProp
        );
        // 對事件進行監聽
        // 比較好奇的是舊的 propKey 若存在着事件監聽,這裏彷佛沒有作什麼處理
        // 這樣不就內存溢出了嗎?難道說不會有這種狀況???
        // 想多了啦,更新 props 的狀況,一樣的事件會被覆蓋
        // 在對應 this._rootNodeID 的狀況下。(但願如此,沒有證明過,可是理解如此)
      } else if (registrationNames[propKey]) {
        putListener(this._rootNodeID, propKey, nextProp);
      } else {
        // 剩餘的就是更新 DOM 屬性啦
        ReactComponent.DOMIDOperations.updatePropertyByID(
          this._rootNodeID,
          propKey,
          nextProp
        );
      }
    }
  },
  _updateDOMChildren: function(nextProps, transaction) {
    // 來更新 DOM 子節點了
    // 當前 this.props.content 類型
    var thisPropsContentType = typeof this.props.content;
    // 是否 thisPropsContentEmpty 爲空
    var thisPropsContentEmpty =
      this.props.content == null || thisPropsContentType === 'boolean';
    // 新的 nextProps.content 類型
    var nextPropsContentType = typeof nextProps.content;
    // 是否 nextPropsContentEmpty 爲空
    var nextPropsContentEmpty =
      nextProps.content == null || nextPropsContentType === 'boolean';

    // 最後使用的 content :
    // 若 thisPropsContentEmpty 不爲空則取 this.props.content 不然
    // this.props.children 類型爲 string 或 number 的狀況下取 this.props.children 不然
    // null
    var lastUsedContent = !thisPropsContentEmpty ? this.props.content :
      CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;

    // 使用內容 content :
    // 若 nextPropsContentEmpty 不爲空則取 nextProps.content 不然
    // nextProps.children 類型爲 string 或 number 的狀況下取 nextProps.children 不然
    // null
    var contentToUse = !nextPropsContentEmpty ? nextProps.content :
      CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;

    // Note the use of `!=` which checks for null or undefined.

    // 最後使用的 children :
    // 若 lastUsedContent 不爲 null or undefined 則取 null 不然
    // 取 this.props.children ,以 content 優先
    var lastUsedChildren =
      lastUsedContent != null ? null : this.props.children;
    // 使用 children :
    // 若 contentToUse 不爲 null or undefined 則取 null 不然
    // 取 nextProps.children ,以 content 優先
    var childrenToUse = contentToUse != null ? null : nextProps.children;

    // 須要使用 content 狀況
    if (contentToUse != null) {
      // 是否須要移除 children 判斷結果:
      // 最後使用的 children 存在而且 children 再也不須要使用
      var childrenRemoved = lastUsedChildren != null && childrenToUse == null;
      if (childrenRemoved) {
        // 更新子節點
        this.updateMultiChild(null, transaction);
      }
      // 若沒知足上面條件則說明不須要更新掉 children
      // 而且新舊 content 不相等的狀況下進行 DOM 操做
      if (lastUsedContent !== contentToUse) {
        ReactComponent.DOMIDOperations.updateTextContentByID(
          this._rootNodeID,
          '' + contentToUse
        );
      }
    } else {
      // 反之看是否須要移除 content
      // 若最後使用的 content 存在且 content 再也不須要使用
      var contentRemoved = lastUsedContent != null && contentToUse == null;
      if (contentRemoved) {
        // 進行 DOM 操做
        ReactComponent.DOMIDOperations.updateTextContentByID(
          this._rootNodeID,
          ''
        );
      }
      // 更新子節點
      // 壓扁更新,與掛載時同樣
      this.updateMultiChild(flattenChildren(nextProps.children), transaction);
    }
  },
};
複製代碼

Diff

關於 DOM 操做一系列的方法這裏不許備作解讀,能夠直接查看源碼 core/ReactDOMIDOperations.js ,道理都是同樣的。可是,這裏須要看下 updateMultiChild 方法,由於這裏已經涉及到 Diff 實現,可是在講 Diff 以前,咱們先把 ReactTextComponent.receiveProps 給解讀掉,其實方法裏面很簡單,就是操做了 ReactDOMIDOperations 相關的方法,具體實現直接看源碼就行,那麼接下來,咱們來看到 updateMultiChildless

// core/ReactMultiChild.js
// 直接看到 updateMultiChild
var ReactMultiChildMixin = {
  enqueueMarkupAt: function(markup, insertAt) {
    this.domOperations = this.domOperations || [];
    this.domOperations.push({insertMarkup: markup, finalIndex: insertAt});
  },
  enqueueMove: function(originalIndex, finalIndex) {
    this.domOperations = this.domOperations || [];
    this.domOperations.push({moveFrom: originalIndex, finalIndex: finalIndex});
  },
  enqueueUnmountChildByName: function(name, removeChild) {
    if (ReactComponent.isValidComponent(removeChild)) {
      this.domOperations = this.domOperations || [];
      this.domOperations.push({removeAt: removeChild._domIndex});
      removeChild.unmountComponent && removeChild.unmountComponent();
      delete this._renderedChildren[name];
    }
  },

  /** * Reconciles new children with old children in three phases. * * - Adds new content while updating existing children that should remain. * - Remove children that are no longer present in the next children. * - As a very last step, moves existing dom structures around. * - (Comment 1) `curChildrenDOMIndex` is the largest index of the current * rendered children that appears in the next children and did not need to * be "moved". * - (Comment 2) This is the key insight. If any non-removed child's previous * index is less than `curChildrenDOMIndex` it must be moved. * * @param {?Object} children Flattened children object. */
  updateMultiChild: function(nextChildren, transaction) {
    // 一些補全判斷操做
    if (!nextChildren && !this._renderedChildren) {
      return;
    } else if (nextChildren && !this._renderedChildren) {
      this._renderedChildren = {}; // lazily allocate backing store with nothing
    } else if (!nextChildren && this._renderedChildren) {
      nextChildren = {};
    }
    // 用於更新子節點時,記錄的父節點 ID 前綴加 dot
    var rootDomIdDot = this._rootNodeID + '.';
    // DOM markup 標記緩衝
    var markupBuffer = null;  // Accumulate adjacent new children markup.
    // DOM markup 標記緩衝等待插入的數量
    var numPendingInsert = 0; // How many root nodes are waiting in markupBuffer
    // 新子節點的循環用索引 index
    var loopDomIndex = 0;     // Index of loop through new children.
    var curChildrenDOMIndex = 0;  // See (Comment 1)
    // 遍歷新的 children
    for (var name in nextChildren) {
      if (!nextChildren.hasOwnProperty(name)) {continue;}
      var curChild = this._renderedChildren[name];
      var nextChild = nextChildren[name];
      // 經過 constructor 來判斷 curChild 和 nextChild 是否爲同一個
      if (shouldManageExisting(curChild, nextChild)) {
        if (markupBuffer) {
          // 若 DOM markup 標記緩衝存在,將其加入隊列
          // 標記位置爲 loopDomIndex - numPendingInsert
          // 這裏和下面是同樣的道理,請看到循環結束後
          this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
          // 清空 DOM markup 標記緩衝
          markupBuffer = null;
        }
        // 初始化 DOM markup 標記緩衝等待插入的數量爲 0
        numPendingInsert = 0;
        // _domIndex 在掛載中依次按照順序進行排序,若他小於目前的子節點順序
        // 則進行移動操做,移動操做則是記錄原 index 和現 index (也就是新子節點的循環用索引 index )
        if (curChild._domIndex < curChildrenDOMIndex) { // (Comment 2)
          // 我沒有辦法聯想到此狀況
          this.enqueueMove(curChild._domIndex, loopDomIndex);
        }
        // curChildrenDOMIndex 則取大值
        curChildrenDOMIndex = Math.max(curChild._domIndex, curChildrenDOMIndex);
        // 硬核式遞歸更新!!一樣會進入到 Diff
        !nextChild.props.isStatic &&
          curChild.receiveProps(nextChild.props, transaction);
        // 更新 _domIndex 屬性
        curChild._domIndex = loopDomIndex;
      } else {
        // 若 curChild 和 nextChild 不爲同一個的時候
        if (curChild) {               // !shouldUpdate && curChild => delete
          // 卸載舊子節點加入隊列,並操做卸載組件
          this.enqueueUnmountChildByName(name, curChild);
          // curChildrenDOMIndex 則取大值
          curChildrenDOMIndex =
            Math.max(curChild._domIndex, curChildrenDOMIndex);
        }
        if (nextChild) {              // !shouldUpdate && nextChild => insert
          // 對應位置傳入新子節點
          this._renderedChildren[name] = nextChild;
          // 生成新的 markup 標記
          // ID 爲父 ID 加 dot 加如今的 name
          var nextMarkup =
            nextChild.mountComponent(rootDomIdDot + name, transaction);
          // 累加 DOM markup 標記緩衝
          markupBuffer = markupBuffer ? markupBuffer + nextMarkup : nextMarkup;
          // DOM markup 標記緩衝等待插入的數量
          numPendingInsert++;
          // 新的子節點 _domIndex 更新
          nextChild._domIndex = loopDomIndex;
        }
      }
      // 若新子節點存在,則新子節點的循環用索引 index 累加 1
      loopDomIndex = nextChild ? loopDomIndex + 1 : loopDomIndex;
    }
    if (markupBuffer) {
      // 將 DOM markup 標記緩衝加入隊列
      // 這裏的 loopDomIndex - numPendingInsert 能夠解釋下
      // 會使得 markupBuffer 存在的狀況就是進入第二個分支,那麼一樣的,
      // 會使得 numPendingInsert 增長的狀況也是第二個分支,那麼在這裏插入的 DOM markup 標記
      // 是最後插入的,他須要從整個循環 DOM 索引減去等待數量來肯定插入位置
      // 舉個例子,你在進入第二個分支時,舊節點存在的狀況下必定會被移除
      // 新節點存在的狀況下必定會被生成 DOM markup 標記 而且累加相應的數量
      // loopDomIndex 也會隨之增長,loopDomIndex 也必定大於等於 numPendingInsert
      // 如:舊節點 <div></div><p></p>
      // 新節點 <div></div><span></span><p></p>
      // 這種狀況下 loopDomIndex 爲 3 , numPendingInsert 爲 2 ,插入位置爲 1
      this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
    }
    // 遍歷舊 children
    for (var childName in this._renderedChildren) { // from other direction
      if (!this._renderedChildren.hasOwnProperty(childName)) { continue; }
      var child = this._renderedChildren[childName];
      if (child && !nextChildren[childName]) {
        // 舊的存在,新的不存在加入隊列
        this.enqueueUnmountChildByName(childName, child);
      }
    }
    // 執行 DOM 操做隊列
    this.processChildDOMOperationsQueue();
  },
  processChildDOMOperationsQueue: function() {
    if (this.domOperations) {
      // 執行隊列
      ReactComponent.DOMIDOperations
        .manageChildrenByParentID(this._rootNodeID, this.domOperations);
      this.domOperations = null;
    }
  },
};
複製代碼

在上面這個執行隊列,咱們須要看到相關的 DOM 操做:dom

// domUtils/DOMChildrenOperations.js
var MOVE_NODE_AT_ORIG_INDEX = keyOf({moveFrom: null});
var INSERT_MARKUP = keyOf({insertMarkup: null});
var REMOVE_AT = keyOf({removeAt: null});

var manageChildren = function(parent, childOperations) {
  // 用於得到 DOM 中原生的 Node
  // 符合 MOVE_NODE_AT_ORIG_INDEX 和 REMOVE_AT
  var nodesByOriginalIndex = _getNodesByOriginalIndex(parent, childOperations);
  if (nodesByOriginalIndex) {
    // 移除對應的 Node
    _removeChildrenByOriginalIndex(parent, nodesByOriginalIndex);
  }
  // 對應的插入
  _placeNodesAtDestination(parent, childOperations, nodesByOriginalIndex);
};
複製代碼

refs 引用

那麼到此, Diff 實現算是解讀完成,最後關於 ref 咱們在這裏也直接解讀掉, ref 爲引用,看到官方註釋:「 ReactOwners are capable of storing references to owned components. 」,那麼首先咱們得知道 [OWNER] 是什麼,他是:「引用組件全部者的屬性鍵。」,那麼他的值就是該組件的全部者(也就是父組件實例),這句話的依據在哪裏呢?函數

// core/ReactCompositeComponent.js
var ReactCompositeComponentMixin = {
  _renderValidatedComponent: function() {
    // render 方法執行前,咱們將 this 也就是當前複合組件傳入 ReactCurrentOwner.current
    // render 方法執行結束後,咱們將置空 ReactCurrentOwner.current
    ReactCurrentOwner.current = this;
    var renderedComponent = this.render();
    ReactCurrentOwner.current = null;
    return renderedComponent;
  },
};
複製代碼

那麼執行 render 方法時,發生了什麼?回憶一下。返回的是 ReactCompositeComponent 或者 ReactNativeComponent 或者 ReactTextComponent ,那麼他們在被實例化的過程當中得到了 ReactCurrentOwner.currentoop

// core/ReactComponent.js
var ReactComponent = {
  Mixin: {
    construct: function(initialProps, children) {
      // Record the component responsible for creating this component.
      // 記錄負責建立此組件的組件。
      // 將其記錄下來。
      this.props[OWNER] = ReactCurrentOwner.current;
    },
  }
};
複製代碼

那麼講了這麼多,他和 ref 有什麼關係呢,那還確實有關係。在掛載、更新、卸載組件時都會發生 ref 的更新,若你對子組件添加了 ref 屬性,那麼他對應的鍵會出如今他擁有者的 this.refs 上,那麼你就能夠經過擁有者調用引用上的方法。學習

那麼到此,實現組件更新。

相關文章
相關標籤/搜索