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

資訊專欄INFORMATION COLUMN

React 源碼深度解讀(九):單個元素更新

kid143 / 2891人閱讀

摘要:作為聲明式的框架,接管了所有頁面更新相關(guān)的操作。是用于內(nèi)部操作的實例,這里將它的初始化為空數(shù)組并插入一個新的。連續(xù)次后,期望的結(jié)果應(yīng)該是。原因很簡單,因為次的時候,取到的都是在完后不會同步更新。

前言

React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學(xué)習(xí) React 源碼的過程中,給我?guī)椭畲蟮木褪沁@個系列文章,于是決定基于這個系列文章談一下自己的理解。本文會大量用到原文中的例子,想體會原汁原味的感覺,推薦閱讀原文。

本系列文章基于 React 15.4.2 ,以下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務(wù) - Part 1
React 源碼深度解讀(八):事務(wù) - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解

正文

在前面的系列文章里,已經(jīng)對 React 的首次渲染和 事務(wù)(transaction)作了比較詳細(xì)的介紹,接下來終于講到它最核心的一個方法:setState。作為聲明式的框架,React 接管了所有頁面更新相關(guān)的操作。我們只需要定義好狀態(tài)和UI的映射關(guān)系,然后根據(jù)情況改變狀態(tài),它自然就能根據(jù)最新的狀態(tài)將頁面渲染出來,開發(fā)者不需要接觸底層的 DOM 操作。狀態(tài)的變更靠的就是setState這一方法,下面我們來揭開它神秘的面紗。

二、setState

介紹開始前,先更新一下例子:

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      desc: "start",
      color: "blue"
    };
    this.timer = setTimeout(
      () => this.tick(),
      5000
    );
  }

  tick() {
    this.setState({
      desc: "end",
      color: "green"
    });
  }

  render() {
    const {desc, color} = this.state;
    
    return (
      

"Welcom to React"

{ desc }

); } } export default App;

state 保存了一個文本信息和顏色,5秒后觸發(fā)更新,改變對應(yīng)的文本與樣式。

下面我們來看下setState的源碼:

function ReactComponent(props, context, updater) {
    this.props = props;
    this.context = context;
    this.refs = emptyObject;
    // We initialize the default updater but the real one gets injected by the
    // renderer.
    this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.setState = function (partialState, callback) {
    this.updater.enqueueSetState(this, partialState);
    if (callback) {
        this.updater.enqueueCallback(this, callback, "setState");
    }
};

這里的updater也是通過依賴注入的方式,在組件實例化的時候注入進(jìn)來的。相關(guān)代碼如下:

// ReactCompositeComponent.js
mountComponent: function (
        transaction,
        hostParent,
        hostContainerInfo,
        context
    ) {
        ...

        // 這里的 transaction 是 ReactReconcileTransaction
        var updateQueue = transaction.getUpdateQueue();

        var doConstruct = shouldConstruct(Component);
        
        // 在這個地方將 updater 注入
        var inst = this._constructComponent(
            doConstruct,
            publicProps,
            publicContext,
            updateQueue
        );
        
        ...
      }

// ReactReconcileTransaction.js
var ReactUpdateQueue = require("ReactUpdateQueue");

getUpdateQueue: function () {
    return ReactUpdateQueue;
}

// ReactUpdateQuene.js
var ReactUpdates = require("ReactUpdates");

enqueueSetState: function (publicInstance, partialState) {
    ...

    var internalInstance = getInternalInstanceReadyForUpdate(
        publicInstance,
        "setState"
    );

    if (!internalInstance) {
        return;
    }

    var queue =
        internalInstance._pendingStateQueue ||
        (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
},

function enqueueUpdate(internalInstance) {
   ReactUpdates.enqueueUpdate(internalInstance);
}

this.updater.enqueueSetState最終落地的代碼是ReactUpdates.enqueueUpdate。internalInstance是用于內(nèi)部操作的 ReactCompositeComponent 實例,這里將它的_pendingStateQueue初始化為空數(shù)組并插入一個新的 state({desc:"end",color:"green"})。

結(jié)合之前 transaction 的內(nèi)容,調(diào)用關(guān)系如下:

三、Transaction 最終操作

從上面的調(diào)用關(guān)系圖可以看出,transaction 最終會調(diào)用 ReactUpdates 的 runBatchedUpdates 方法。

function runBatchedUpdates(transaction) {
    var len = transaction.dirtyComponentsLength;
    
    ...

    for (var i = 0; i < len; i++) {
        var component = dirtyComponents[i];

        ...
        
        ReactReconciler.performUpdateIfNecessary(
            component,
            transaction.reconcileTransaction,
            updateBatchNumber
        );

        ...
    }
}

接著是調(diào)用 ReactReconciler 的 performUpdateIfNecessary,然后到 ReactCompositeComponent 的一系列方法:

performUpdateIfNecessary: function (transaction) {
    if (this._pendingElement != null) {
        ReactReconciler.receiveComponent(
            this,
            this._pendingElement,
            transaction,
            this._context
        );
    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
        this.updateComponent(
            transaction,
            this._currentElement,
            this._currentElement,
            this._context,
            this._context
        );
    } else {
        this._updateBatchNumber = null;
    }
},

updateComponent: function (
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext
) {
    var inst = this._instance;

    ...

    var nextState = this._processPendingState(nextProps, nextContext);

    ...
   
    this._performComponentUpdate(
        nextParentElement,
        nextProps,
        nextState,
        nextContext,
        transaction,
        nextUnmaskedContext
    );
   
},

_processPendingState: function (props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    
    ...

    var nextState = Object.assign({}, replace ? queue[0] : inst.state);
    
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
        var partial = queue[i];
        
        Object.assign(
            nextState,
            typeof partial === "function" ?
            partial.call(inst, nextState, props, context) :
            partial
        );
    }

    return nextState;
},

_performComponentUpdate: function (
    nextElement,
    nextProps,
    nextState,
    nextContext,
    transaction,
    unmaskedContext
) {
    var inst = this._instance;

    ...

    this._updateRenderedComponent(transaction, unmaskedContext);

    ...
},

/**
 * Call the component"s `render` method and update the DOM accordingly.
 */
_updateRenderedComponent: function (transaction, context) {
    // ReactDOMComponent
    var prevComponentInstance = this._renderedComponent;
    
    // 上一次的Virtual DOM(ReactElement)
    var prevRenderedElement = prevComponentInstance._currentElement;
    
    // 調(diào)用 render 獲取最新的Virtual DOM(ReactElement)
    var nextRenderedElement = this._renderValidatedComponent();

    ...

    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
        ReactReconciler.receiveComponent(
            prevComponentInstance,
            nextRenderedElement,
            transaction,
            this._processChildContext(context)
        );
    }
    
    ...
},

這里最重要的方法分別為_processPendingState_updateRenderedComponent。_processPendingState是真正更新 state 的地方,可以看到它其實就是一個Object.assign的過程。在實際開發(fā)過程中,如果需要基于之前的 state 值連續(xù)進(jìn)行運算的話,如果直接通過對象去 setState 往往得到的結(jié)果是錯誤的,看以下例子:

// this.state.count = 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});

假設(shè) count 的初始值是 0 。連續(xù) 3 次 setState 后,期望的結(jié)果應(yīng)該是 3 。但實際得到的值是 1 。原因很簡單,因為 3 次 setState 的時候,取到的this.state.count都是 0 (state 在 set 完后不會同步更新)。如果想得到期望的結(jié)果,代碼要改成下面的樣子:

function add(nextState, props, context) {
    return {count: nextState.count + 1};
}

this.setState(add);
this.setState(add);
this.setState(add);

結(jié)合源碼來看,如果 setState 的參數(shù)類型是 function,每次合并后的nextState都會作為參數(shù)傳入,得到的結(jié)果自然是正確的了:

Object.assign(
    nextState,
    typeof partial === "function"
        ? partial.call(inst, nextState, props, context)
        : partial,
);

_updateRenderedComponent會取出實例的 ReactDOMComponent,然后調(diào)用 render 方法,得出最新的 Virtual DOM 后啟動 Diff 的過程。

四、Diff

ReactReconciler.receiveComponent最終會調(diào)用 ReactDOMComponent 的 receiveComponent 方法,進(jìn)而再調(diào)用 updateComponent 方法:

updateComponent: function (transaction, prevElement, nextElement, context) {
    var lastProps = prevElement.props;
    var nextProps = this._currentElement.props;
    
    ...

    this._updateDOMProperties(lastProps, nextProps, transaction);
    
    this._updateDOMChildren(
        lastProps,
        nextProps,
        transaction,
        context
    );

    ...
},

這個方法只有 2 個操作,一個是更新屬性,另一個是更新子孫結(jié)點。先來看看更新屬性的操作:

_updateDOMProperties: function (lastProps, nextProps, transaction) {
    var propKey;
    var styleName;
    var styleUpdates;

    // 刪除舊的屬性
    for (propKey in lastProps) {
        // 篩選出后來沒有但之前有的屬性
        if (nextProps.hasOwnProperty(propKey) ||
            !lastProps.hasOwnProperty(propKey) ||
            lastProps[propKey] == null) {
            continue;
        }
        if (propKey === STYLE) {
            var lastStyle = this._previousStyleCopy;
            // 初始化 styleUpdates,之前所有的 style 屬性設(shè)置為空
            for (styleName in lastStyle) {
                // 將舊的 style 屬性設(shè)置為空
                if (lastStyle.hasOwnProperty(styleName)) {
                    styleUpdates = styleUpdates || {};
                    styleUpdates[styleName] = "";
                }
            }
            this._previousStyleCopy = null;
        } 
        ...
        } else if (
            DOMProperty.properties[propKey] ||
            DOMProperty.isCustomAttribute(propKey)) {
            DOMPropertyOperations.deleteValueForProperty(getNode(
                this), propKey);
        }
    }

    for (propKey in nextProps) {
        var nextProp = nextProps[propKey];
        var lastProp =
            propKey === STYLE ? this._previousStyleCopy :
            lastProps != null ? lastProps[propKey] : undefined;

        // 值相等則跳過
        if (!nextProps.hasOwnProperty(propKey) ||
            nextProp === lastProp ||
            nextProp == null && lastProp == null) {
            continue;
        }

        if (propKey === STYLE) {
            if (nextProp) {
                nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
            } else {
                this._previousStyleCopy = null;
            }
            if (lastProp) {
                // Unset styles on `lastProp` but not on `nextProp`.
                for (styleName in lastProp) {
                    if (lastProp.hasOwnProperty(styleName) &&
                        (!nextProp || !nextProp.hasOwnProperty(styleName))) {
                        styleUpdates = styleUpdates || {};
                        styleUpdates[styleName] = "";
                    }
                }
                // Update styles that changed since `lastProp`.
                for (styleName in nextProp) {
                    if (nextProp.hasOwnProperty(styleName) &&
                        lastProp[styleName] !== nextProp[styleName]
                    ) {
                        styleUpdates = styleUpdates || {};
                        styleUpdates[styleName] = nextProp[
                            styleName];
                    }
                }
            } else {
                // Relies on `updateStylesByID` not mutating `styleUpdates`.
                styleUpdates = nextProp;
            }
        }
        
        ...
        
        } else if (
            DOMProperty.properties[propKey] ||
            DOMProperty.isCustomAttribute(propKey)) {
            var node = getNode(this);
            // If we"re updating to null or undefined, we should remove the property
            // from the DOM node instead of inadvertently setting to a string. This
            // brings us in line with the same behavior we have on initial render.
            if (nextProp != null) {
                DOMPropertyOperations.setValueForProperty(node,
                    propKey, nextProp);
            } else {
                DOMPropertyOperations.deleteValueForProperty(node,
                    propKey);
            }
        }
    }
    if (styleUpdates) {
        CSSPropertyOperations.setValueForStyles(
            getNode(this),
            styleUpdates,
            this
        );
    }
},

這里主要有 2 個循環(huán),第一個循環(huán)刪除舊的屬性,第二個循環(huán)設(shè)置新的屬性。屬性的刪除靠的是DOMPropertyOperations.deleteValueForPropertyDOMPropertyOperations.deleteValueForAttribute,屬性的設(shè)置靠的是DOMPropertyOperations.setValueForPropertyDOMPropertyOperations.setValueForAttribute。以 setValueForAttribute 為例子,最終是調(diào)用 DOM 的 api :

setValueForAttribute: function (node, name, value) {
    if (!isAttributeNameSafe(name)) {
        return;
    }
    if (value == null) {
        node.removeAttribute(name);
    } else {
        node.setAttribute(name, "" + value);
    }
},

針對 style 屬性,由styleUpdates這個對象來收集變化的信息。它會先將舊的 style 內(nèi)的所有屬性設(shè)置為空,然后再用新的 style 來填充。得出新的 style 后調(diào)用CSSPropertyOperations.setValueForStyles來更新:

setValueForStyles: function (node, styles, component) {
    var style = node.style;
    
    for (var styleName in styles) {
        ...
        
        if (styleValue) {
            style[styleName] = styleValue;
        } else {
            ...
            
            style[styleName] = "";
        }
    }
},

接下來看 updateDOMChildren 。

updateDOMChildren: function (lastProps, nextProps, transaction,
    context) {
    var lastContent =
        CONTENT_TYPES[typeof lastProps.children] ? lastProps.children :
        null;
    var nextContent =
        CONTENT_TYPES[typeof nextProps.children] ? nextProps.children :
        null;

    ...

    if (nextContent != null) {
        if (lastContent !== nextContent) {
            this.updateTextContent("" + nextContent);
        }
    }
    
    ...
},

結(jié)合我們的例子,最終會調(diào)用updateTextContent。這個方法來自 ReactMultiChild ,可以簡單理解為 ReactDOMComponent 繼承了 ReactMultiChild 。

updateTxtContent: function (nextContent) {
    var prevChildren = this._renderedChildren;
    // Remove any rendered children.
    ReactChildReconciler.unmountChildren(prevChildren, false);
    
    for (var name in prevChildren) {
        if (prevChildren.hasOwnProperty(name)) {
            invariant(false,
                "updateTextContent called on non-empty component."
            );
        }
    }
    // Set new text content.
    var updates = [makeTextContent(nextContent)];
    processQueue(this, updates);
},

function makeTextContent(textContent) {
    // NOTE: Null values reduce hidden classes.
    return {
        type: "TEXT_CONTENT",
        content: textContent,
        fromIndex: null,
        fromNode: null,
        toIndex: null,
        afterNode: null,
    };
},

function processQueue(inst, updateQueue) {
    ReactComponentEnvironment.processChildrenUpdates(
        inst,
        updateQueue,
    );
}

這里的 ReactComponentEnvironment 通過依賴注入的方式注入后,實際上是 ReactComponentBrowserEnvironment 。最終會調(diào)用 DOMChildrenOperations 的 processUpdates:

processUpdates: function (parentNode, updates) {
    for (var k = 0; k < updates.length; k++) {
        var update = updates[k];
        switch (update.type) {
            ...
            
            case "TEXT_CONTENT":
                setTextContent(
                    parentNode,
                    update.content
                );
                if (__DEV__) {
                    ReactInstrumentation.debugTool.onHostOperation({
                        instanceID: parentNodeDebugID,
                        type: "replace text",
                        payload: update.content.toString(),
                    });
                }
                break;
            ...
        }
    }
},

// setTextContent.js
var setTextContent = function(node, text) {
  if (text) {
    var firstChild = node.firstChild;
    
    if (firstChild && firstChild === node.lastChild && firstChild.nodeType === 3) {
      firstChild.nodeValue = text;
      return;
    }
  }
  node.textContent = text;
};

最終的調(diào)用關(guān)系見下圖:

五、總結(jié)

本文將 setState 的整個流程從頭到尾走了一遍,下一篇將會詳細(xì)的介紹 Diff 的策略。

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

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

相關(guān)文章

  • React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3

    摘要:在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個系列文章,于是決定基于這個系列文章談一下自己的理解。到此為止,首次渲染就完成啦總結(jié)從啟動到元素渲染到頁面,并不像看起來這么簡單,中間經(jīng)歷了復(fù)雜的層級調(diào)用。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過...

    U2FsdGVkX1x 評論0 收藏0
  • React 源碼深度解讀(八):事務(wù) - Part 2

    摘要:前言是一個十分龐大的庫,由于要同時考慮和,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個系列文章,于是決定基于這個系列文章談一下自己的理解。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常...

    airborne007 評論0 收藏0
  • React 源碼深度解讀(六):依賴注入

    摘要:依賴注入和控制反轉(zhuǎn),這兩個詞經(jīng)常一起出現(xiàn)。一句話表述他們之間的關(guān)系依賴注入是控制反轉(zhuǎn)的一種實現(xiàn)方式。而兩者有大量的代碼都是可以共享的,這就是依賴注入的使用場景了。下一步就是創(chuàng)建具體的依賴內(nèi)容,然后注入到需要的地方這里的等于這個對象。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級...

    glumes 評論0 收藏0
  • React 源碼深度解讀(四):首次自定義組件渲染 - Part 1

    摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結(jié)構(gòu)如下經(jīng)過編譯后,生成如下代碼構(gòu)建頂層包裝組件跟普通元素渲染一樣,第一步先會執(zhí)行創(chuàng)建為的。調(diào)用順序已在代碼中注釋。先看圖,這部分內(nèi)容將在下回分解 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非...

    Warren 評論0 收藏0
  • React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2

    摘要:本文將要講解的調(diào)用棧是下面這個樣子的平臺無關(guān)相關(guān)如果看源碼,我們會留意到很多相關(guān)的代碼,我們暫時先將其忽略,會在后續(xù)的文章中進(jìn)行講解。現(xiàn)在我們來看一下各實例間的關(guān)系目前為止的調(diào)用棧平臺無關(guān)相關(guān)下一篇講解三總結(jié)本文講解了轉(zhuǎn)化為的過程。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 R...

    wean 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<