React原始碼分析與實現(一):元件的初始化與渲染

Neal_yang發表於2018-08-27

原文連結地址:github.com/Nealyang 轉載請註明出處

前言

戰戰兢兢寫下開篇...也感謝小蘑菇大神以及網上各路大神的部落格資料參考~

閱讀原始碼的方式有很多種,廣度優先法、呼叫棧除錯法等等,此係列文章,採用基線法,顧名思義,就是以低版本為基線,逐漸瞭解原始碼的演進過程和思路。

react最初的設計靈感來源於遊戲渲染的機制:當資料變化時,介面僅僅更新變化的部分而形成新的一幀渲染。所以設計react的核心就是認為UI只是把資料通過對映關係變換成另一種形式的資料,也就是展示方式。傳統上,web架構使用模板或者HTML指令構造頁面。react則處理構建使用者介面通過將他們份極為virtual dom,當然這也是react的核心,整個react架構的設計理念也是為此展開的。

準備工作

我們採用基線法去學習react原始碼,所以目前基於的版本為stable-0.3,後面我們在逐步分析學習演變的版本。

clone程式碼

git clone https://github.com/facebook/react.git

git checkout 0.3-stable
複製程式碼

IMAGE

React原始碼都在src目錄中,src包含了8個目錄,其主要內容描述見下表。

目 錄 內容
core React 核心類
domUtil Dom操作和CSS操作的相關工具類
environment 當前JS執行環境的基本資訊
event React事件機制的核心類
eventPlugins React事件機制的事件繫結外掛類
test 測試目錄
utils 各種工具類
vendor 可替換模組存放目錄

IMAGE

我們將該版本編譯後的程式碼放到example下,引入到basic/index.html中執行除錯。

元件初始化

使用

這裡還是以basic.html中的程式碼為例

<script>
      var ExampleApplication = React.createClass({
        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);
        }
      });
      var start = new Date().getTime();
      setInterval(function() {
        React.renderComponent(
          ExampleApplication({elapsed: new Date().getTime() - start}),
          document.getElementById('container')
        );
      }, 50);
    </script>
複製程式碼

回到我們說的元件初始化,抽離下上面的程式碼就是:

var ExampleApplication = React.createClass({render:function(){ return <div>Nealyang</div> }})
複製程式碼

熟悉react使用的人都知道,render方法不能為空,當然,createClass中我們也可以去寫一寫生命週期的鉤子函式,這裡我們暫且省略,畢竟目前我們更加的關注react組建的初始化過程。

同樣,熟悉react使用方法的人也會有疑惑了,怎麼例項程式碼中的render最後return的是React.DOM.p(null,message)

所以到這裡,就不得不說一下react的編譯階段了

編譯階段

我們都知道,在js中直接編寫html程式碼,或者。。。jsx語法這樣的AST,在js詞法分析階段就會丟擲異常的。

對的,所以我們在編寫react程式碼的時候都會藉助babel去轉碼

從babel官網上寫個例子即可看出:

IMAGE

對呀!明明人家用的是react.createElement方法,我們怎麼出現個React.DOM.p...

OK,歷史原因:

IMAGE

  • react現在版本中,使用babel-preset-react來編譯jsx,這個preset又包含了4個外掛,其中transform-react-jsx負責編譯jsx,呼叫了React.createElement函式生成虛擬元件
  • 在react-0.3裡,編譯結果稍稍有些不同,官方給出的示例檔案,使用JSXTransformer.js編譯jsx(也就是<script src="../source/JSXTransformer.js"></script>),對於native元件和composite元件編譯的方式也不一致。也就是我們看到的React.DOM.p or ReactComponsiteComponent
    • native元件:編譯成React.DOM.xxx(xxx如div),函式執行返回一個ReactNativeComponent例項。
    • composite元件:編譯成createClass返回的函式呼叫,函式執行返回一個ReactCompositeComponent例項

題外話,不管用什麼框架,到瀏覽器這部分的,什麼花裡胡哨的都不復存在。我這就是js、css、html。所以我們這裡的ReactCompositeComponent最終其實還是需要轉成原生元素的 。\

元件建立

從React.js中我們可以找到createClass的出處:


"use strict";

var ReactCompositeComponent = require('ReactCompositeComponent');

...

var React = {
...
  createClass: ReactCompositeComponent.createClass,
...
};

module.exports = React;
複製程式碼
  • createClass 程式碼
  var ReactCompositeComponentBase = function() {};
  
  function mixSpecIntoComponent(Constructor, spec) {
  var proto = Constructor.prototype;
  for (var name in spec) {
    if (!spec.hasOwnProperty(name)) {
      continue;
    }
    var property = spec[name];
    var specPolicy = ReactCompositeComponentInterface[name];


    if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
      RESERVED_SPEC_KEYS[name](Constructor, property);
    } else if (property && property.__reactAutoBind) {
      if (!proto.__reactAutoBindMap) {
        proto.__reactAutoBindMap = {};
      }
      proto.__reactAutoBindMap[name] = property.__reactAutoBind;
    } else if (proto.hasOwnProperty(name)) {
      // For methods which are defined more than once, call the existing methods
      // before calling the new property.
      proto[name] = createChainedFunction(proto[name], property);
    } else {
      proto[name] = property;
    }
  }
}

         createClass: function (spec) {
            var Constructor = function (initialProps, children) {
              this.construct(initialProps, children);
            };
            // ReactCompositeComponentBase是React複合元件的原型函式
            Constructor.prototype = new ReactCompositeComponentBase();
            Constructor.prototype.constructor = Constructor;
            // 把消費者宣告配置spec合併到Constructor.prototype中
            mixSpecIntoComponent(Constructor, spec);
            // 判斷合併後的結果有沒有render,如果沒有 render,丟擲一個異常
            invariant(
              Constructor.prototype.render,
              'createClass(...): Class specification must implement a `render` method.'
            );

            //工廠
            var ConvenienceConstructor = function (props, children) {
              return new Constructor(props, children);
            };
            ConvenienceConstructor.componentConstructor = Constructor;
            ConvenienceConstructor.originalSpec = spec;
            return ConvenienceConstructor;
          },
複製程式碼
  • mixSpecIntoComponent 方法就是講spec的屬性賦值給Constructor的原型上
  • createClass返回一個ConvenienceConstructor建構函式,建構函式接受props、children 建構函式的靜態方法componentConstructor和originalSpec分別指向Constructor和spec。
  • 有種類似於寄生組合式繼承的寫法,Constructor為每一個元件例項的原型(var instance = new Constructor(); instance.construct.apply(instance, arguments);)。Constructor原型指向ReactCompositeComponentBase,又把構造器指向Constructor自己。然後把傳入的spec合併到Constructor.prototype中。判斷合併後的結果有沒有render,如果沒有 render,丟擲一個異常

其實很多人看到這估計都會很疑惑,為毛這樣搞???直接返回個建構函式不就可以了嘛。

其實react在後面做diff演算法的時候,是採用元件的Constructor來判斷元件是否相同的。如此可以保證每個createClass建立出來的元件都是一個新的Constructor。

ok,那麼我直接用寄生繼承呀

// 寫法1
const createClass = function(spec) { 
    var Constructor = function (initialProps, children) {
      this.construct(initialProps, children);
    };
    Constructor.prototype = new ReactCompositeComponentBase();
    Constructor.prototype.constructor = Constructor;
    mixSpecIntoComponent(ReactCompositeComponentBase, spec)
    return Constructor
}
const Table1 = new createClass(spec)(props, children);
//console.log(Table1.constructor)
複製程式碼

為什麼還需要ConvenienceConstructor呢?說實話,我也不知道,然後看了在網上查到相關資訊說道:

上面寫法在大多數情況下並不會產生什麼問題,但是,當團隊裡的人無意中修改錯點什麼,比如:

Table1.prototype.onClick = null
複製程式碼

這樣,所有Table1例項化的元件,onClick全部為修改後的空值

<Table1 />
<Table1 />
複製程式碼

我們知道,js是動態解釋型語言,函式可以執行時被隨意篡改。而靜態編譯語言在執行時期間,函式不可修改(某些靜態語言也可以修改)。所以採用這種方式防禦使用者對程式碼的篡改。

元件例項化

既然createClass返回的是一個建構函式,那麼我們就來看看他的例項化吧

          /**
           * Base constructor for all React component.
           *
           * Subclasses that override this method should make sure to invoke
           * `ReactComponent.Mixin.construct.call(this, ...)`.
           *
           * @param {?object} initialProps
           * @param {*} children
           * @internal
           */
          construct: function (initialProps, children) {
            this.props = initialProps || {};
            if (typeof children !== 'undefined') {
              this.props.children = children;
            }
            // Record the component responsible for creating this component.
            this.props[OWNER] = ReactCurrentOwner.current;
            // All components start unmounted.
            this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
          },
複製程式碼

其實也就是將props、children掛載到this.props上 以及生命週期的設定。這裡暫且不說,因為我也正在看。。。哇咔咔

這裡的

this.props[OWNER] = ReactCurrentOwner.current;
複製程式碼

this.props[OWNER]指的是當前元件的容器(父)元件例項

如果我們直接在basic.html中列印就直接出來的是null,但是如果像如下的方式書寫:

const Children = React.createClass({
    componentDidMount = () => console.log(this.props["{owner}"]),
    render = () => null
})  

const Parent = React.createClass({
    render: () => <Children />
})  
複製程式碼

這裡輸出的就是Parent元件例項。

再看看ReactCurrentOwner.current的賦值就明白了

_renderValidatedComponent: function () {
    ReactCurrentOwner.current = this;
    var renderedComponent = this.render();
    ReactCurrentOwner.current = null;
    invariant(
      ReactComponent.isValidComponent(renderedComponent),
      '%s.render(): A valid ReactComponent must be returned.',
      this.constructor.displayName || 'ReactCompositeComponent'
    );
    return renderedComponent;
}
複製程式碼

可以看出來,在執行render前後,分別設定了ReactCurrentOwner.current的值,這樣就能保證render函式內的子元件能賦上當前元件的例項,也就是this。

元件渲染

我們先撇開事務、事件池、生命週期、diff當然也包括fiber 等,先不談,其實渲染就是將經過babel編譯後的,當然這裡是JSXTransformer.js編譯後的Ojb給寫入到HTML中而已。

export default function render(vnode, parent) {
    let dom;
    if (typeof vnode === 'string') {
        dom = document.createTextNode(vnode);
        // let span_dom = document.createElement('span')
        // span_dom.appendChild(dom);
        // parent.appendChild(span_dom);
        parent.appendChild(dom);
    } else if (typeof vnode.nodeName === 'string') {
        dom = document.createElement(vnode.nodeName);
        setAttrs(dom, vnode.props);
        parent.appendChild(dom)
        for(let i = 0; i < vnode.children.length; i++) {
             render(vnode.children[i], dom)
        }
    }else if(typeof vnode.nodeName === 'function'){
        let innerVnode = vnode.nodeName.prototype.render();
        render(innerVnode,parent)
    }
}

function setAttrs(dom, props) {
    const ALL_KEYS = Object.keys(props);

    ALL_KEYS.forEach(k =>{
        const v = props[k];

        // className
        if(k === 'className'){
            dom.setAttribute('class',v);
            return;
        }
        if(k == "style") {
            if(typeof v == "string") {
                dom.style.cssText = v
            }

            if(typeof v == "object") {
                for (let i in v) {
                    dom.style[i] =  v[i]
                }
            }
            return

        }

        if(k[0] == "o" && k[1] == "n") {
            const capture = (k.indexOf("Capture") != -1)
            dom.addEventListener(k.substring(2).toLowerCase(),v,capture)
            return
        }

        dom.setAttribute(k, v)
    })
}
複製程式碼

是的,就這樣

img

OK,回到原始碼~

img

在我們目前使用的react版本中,渲染呼叫的是ReactDOM.render方法,這裡ReactMount.renderComponent為我們的入口方法。

ReactMount.renderComponent在react初探章節講過。如果元件渲染過,就更新元件屬性,如果元件沒有渲染過,掛載元件事件,並把虛擬元件渲染成真實元件插入container內。通常,我們很少去呼叫兩次renderComponent,所以大多數情況下不會更新元件屬性而是新建立dom節點並插入到container中。

ReactComponent.mountComponentIntoNode之內開啟了一個事務,事務保證渲染階段不會有任何事件觸發,並阻斷的componentDidMount事件,待執行後執行等,事務在功能一章我們會詳細講解,這裡不細討論。 ReactComponent._mountComponentIntoNode這個函式呼叫mountComponent獲得要渲染的innerHTML,然後更新container的innerHTML。 ReactCompositeComponent.mountComponent是最主要的邏輯方法。這個函式內處理了react的生命週期以及componentWillComponent和componentDidMount生命週期鉤子函式,呼叫render返回實際要渲染的內容,如果內容是複合元件,仍然會呼叫mountComponent,複合元件最終一定會返回原生元件, 並且最終呼叫ReactNativeComponent的mountComponent函式生成要渲染的innerHTML。

IMAGE

  renderComponent: function(nextComponent, container) {
    var prevComponent = instanceByReactRootID[getReactRootID(container)];
    if (prevComponent) {
      var nextProps = nextComponent.props;
      ReactMount.scrollMonitor(container, function() {
        prevComponent.replaceProps(nextProps);
      });
      return prevComponent;
    }

    ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);

    var reactRootID = ReactMount.registerContainer(container);
    instanceByReactRootID[reactRootID] = nextComponent;
    nextComponent.mountComponentIntoNode(reactRootID, container);
    return nextComponent;
  },
複製程式碼

這段程式碼邏輯大概就是上面的流程圖,這裡不再贅述。

  • mountComponentIntoNode 從debugger中,可以看出mountComponentIntoNode第一個引數其實傳入的是react分配給元件的一個唯一標識
    IMAGE
    mountComponentIntoNode: function(rootID, container) {
      var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
      transaction.perform(
        this._mountComponentIntoNode,
        this,
        rootID,
        container,
        transaction
      );
      ReactComponent.ReactReconcileTransaction.release(transaction);
    },
複製程式碼

原始碼中,這裡跟事務扯到了關係,其實我們只要關注渲染本身,所以這裡我們直接看this._mountComponentIntoNode的方法實現

  • _mountComponentIntoNode
    _mountComponentIntoNode: function(rootID, container, transaction) {
      var renderStart = Date.now();
      var markup = this.mountComponent(rootID, transaction);
      ReactMount.totalInstantiationTime += (Date.now() - renderStart);

      var injectionStart = Date.now();
      // Asynchronously inject markup by ensuring that the container is not in
      // the document when settings its `innerHTML`.
      var parent = container.parentNode;
      if (parent) {
        var next = container.nextSibling;
        parent.removeChild(container);
        container.innerHTML = markup;
        if (next) {
          parent.insertBefore(container, next);
        } else {
          parent.appendChild(container);
        }
      } else {
        container.innerHTML = markup;
      }
      ReactMount.totalInjectionTime += (Date.now() - injectionStart);
    },
複製程式碼

上述程式碼流程大概如下:

IMAGE

流程的確如上,作為一個初探原始碼者,我當然不關心你到底是在哪innerHTML的,我想知道你是腫麼把jsx編譯後的Obj轉成HTML的哇~

IMAGE

  • ReactCompositeComponent.mountComponent

這裡類變成了ReactCompositeComponent(debugger可以跟蹤每一個函式)

IMAGE

原始碼中的this.mountComponent,為什麼不是呼叫ReactComponent.mountComponent呢?這裡主要使用了多重繼承機制(Mixin,後續講解)。

  mountComponent: function(rootID, transaction) {
  // 掛在元件ref(等於當前元件例項)到this.refs上
    ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);

    // Unset `this._lifeCycleState` until after this method is finished.
    // 這是生命週期
    this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
    this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;

    // 元件宣告有props,執行校驗
    if (this.constructor.propDeclarations) {
      this._assertValidProps(this.props);
    }
    // 為元件宣告時間繫結this
    if (this.__reactAutoBindMap) {
      this._bindAutoBindMethods();
    }
    //獲取state
    this.state = this.getInitialState ? this.getInitialState() : null;
    this._pendingState = null;

    // 如果元件宣告componentWillMount函式,執行並把setState的結果更新到this.state上
    if (this.componentWillMount) {
      this.componentWillMount();
      // When mounting, calls to `setState` by `componentWillMount` will set
      // `this._pendingState` without triggering a re-render.
      if (this._pendingState) {
        this.state = this._pendingState;
        this._pendingState = null;
      }
    }
    // 如果宣告瞭componentDidMount,則把其加入到ReactOnDOMReady佇列中
    if (this.componentDidMount) {
      transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
    }
    
    // 呼叫元件宣告的render函式,並返回ReactComponent抽象類例項(ReactComponsiteComponent或
    // ReactNativeComponent),呼叫相應的mountComponent函式
    this._renderedComponent = this._renderValidatedComponent();

    // Done with mounting, `setState` will now trigger UI changes.
    this._compositeLifeCycleState = null;
    this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;

    return this._renderedComponent.mountComponent(rootID, transaction);
  },
複製程式碼

這個函式式VDom中最為重要的函式,操作也最為複雜,執行操作大概如下:

IMAGE

如上,很多內容跟我們這part有點超綱。當然,後面都會說道,關於react的渲染,其實我們的工作很簡單,不關於任何,在拿到render的東西后,如何解析,其實就是最後一行程式碼:this._renderedComponent.mountComponent(rootID, transaction);

  mountComponent: function(rootID, transaction) {
    ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
    assertValidProps(this.props);
    return (
      this._createOpenTagMarkup() +
      this._createContentMarkup(transaction) +
      this._tagClose
    );
  },
  _createOpenTagMarkup: function() {
    var props = this.props;
    var ret = this._tagOpen;

    for (var propKey in props) {
      if (!props.hasOwnProperty(propKey)) {
        continue;
      }
      var propValue = props[propKey];
      if (propValue == null) {
        continue;
      }
      if (registrationNames[propKey]) {
        putListener(this._rootNodeID, propKey, propValue);
      } else {
        if (propKey === STYLE) {
          if (propValue) {
            propValue = props.style = merge(props.style);
          }
          propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
        }
        var markup =
          DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
        if (markup) {
          ret += ' ' + markup;
        }
      }
    }

    return ret + ' id="' + this._rootNodeID + '">';
  },

  /**
   * Creates markup for the content between the tags.
   *
   * @private
   * @param {ReactReconcileTransaction} transaction
   * @return {string} Content markup.
   */
  _createContentMarkup: function(transaction) {
    // Intentional use of != to avoid catching zero/false.
    var innerHTML = this.props.dangerouslySetInnerHTML;
    if (innerHTML != null) {
      if (innerHTML.__html != null) {
        return innerHTML.__html;
      }
    } else {
      var contentToUse = this.props.content != null ? this.props.content :
        CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
      var childrenToUse = contentToUse != null ? null : this.props.children;
      if (contentToUse != null) {
        return escapeTextForBrowser(contentToUse);
      } else if (childrenToUse != null) {
        return this.mountMultiChild(
          flattenChildren(childrenToUse),
          transaction
        );
      }
    }
    return '';
  },
  function ReactNativeComponent(tag, omitClose) {
  this._tagOpen = '<' + tag + ' ';
  this._tagClose = omitClose ? '' : '</' + tag + '>';
  this.tagName = tag.toUpperCase();
}
複製程式碼

程式碼稍微多一點,但是工作目標很單一,就是為了將描述jsx的obj解析成HTML string。其實可以參照我上面直接亮出來的自己寫的程式碼部分。

如上,其實我們已經完成了元件的初始化、渲染~

img

好吧,我們一直說的渲染的核心部分還沒有細說~~~

掛載元件ref到this.refs上,設定生命週期、狀態和rootID

    mountComponent: function(rootID, transaction) {
      invariant(
        this._lifeCycleState === ComponentLifeCycle.UNMOUNTED,
        'mountComponent(%s, ...): Can only mount an unmounted component.',
        rootID
      );
      var props = this.props;
      if (props.ref != null) {
        ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
      }
      this._rootNodeID = rootID;
      this._lifeCycleState = ComponentLifeCycle.MOUNTED;
      // Effectively: return '';
    },
複製程式碼

如果元件ref屬性為空,則為元件的this.refs上掛在當前元件,也就是this,實現如下:

  addComponentAsRefTo: function(component, ref, owner) {
    owner.attachRef(ref, component);
  }
複製程式碼
    attachRef: function(ref, component) {
      var refs = this.refs || (this.refs = {});
      refs[ref] = component;
    },
複製程式碼

上述程式碼我刪除了相關的判斷警告。

設定元件生命狀態

元件的生命狀態和生命週期鉤子函式是react的兩個概念,在react中存在兩種生命週期

  • 主:元件生命週期:_lifeCycleState,用來校驗react元件在執行函式時狀態值是否正確
  • 輔:複合元件生命週期:_componsiteLifeCycleState,用來保證setState流程不受其他行為影響

_lifeCycleState

var ComponentLifeCycle = keyMirror({
  /**
   * Mounted components have a DOM node representation and are capable of
   * receiving new props.
   */
  MOUNTED: null,
  /**
   * Unmounted components are inactive and cannot receive new props.
   */
  UNMOUNTED: null
});
複製程式碼

元件生命週期非常簡單,就列舉了兩種,MOUNTED and UNMOUNTED

在原始碼中使用其只是為了在相應的階段觸發時候校驗,並且給出錯誤提示

    getDOMNode: function() {
      invariant(
        ExecutionEnvironment.canUseDOM,
        'getDOMNode(): The DOM is not supported in the current environment.'
      );
      invariant(
        this._lifeCycleState === ComponentLifeCycle.MOUNTED,
        'getDOMNode(): A component must be mounted to have a DOM node.'
      );
      var rootNode = this._rootNode;
      if (!rootNode) {
        rootNode = document.getElementById(this._rootNodeID);
        if (!rootNode) {
          // TODO: Log the frequency that we reach this path.
          rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
        }
        this._rootNode = rootNode;
      }
      return rootNode;
    },
複製程式碼

_compositeLifeCycleState

複合元件的生命週期只在一個地方使用:setState

var CompositeLifeCycle = keyMirror({
  /**
   * Components in the process of being mounted respond to state changes
   * differently.
   */
  MOUNTING: null,
  /**
   * Components in the process of being unmounted are guarded against state
   * changes.
   */
  UNMOUNTING: null,
  /**
   * Components that are mounted and receiving new props respond to state
   * changes differently.
   */
  RECEIVING_PROPS: null,
  /**
   * Components that are mounted and receiving new state are guarded against
   * additional state changes.
   */
  RECEIVING_STATE: null
});
複製程式碼
  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;
    }
  },
複製程式碼

setState會呼叫replaceState ,然後呼叫_receivePropsAndState來更新介面

如果元件正處在mounting的過程或者接受props的過程中,那麼將state快取在_pendingState中,並不會更新介面的值。

校驗props

  _assertValidProps: function(props) {
    var propDeclarations = this.constructor.propDeclarations;
    var componentName = this.constructor.displayName;
    for (var propName in propDeclarations) {
      var checkProp = propDeclarations[propName];
      if (checkProp) {
        checkProp(props, propName, componentName);
      }
    }
  }
複製程式碼

this.constructor.propDeclarations 就是元件宣告的props屬性,由於props是執行時傳入的屬性。我們可以看到宣告props的屬性值即為checkProp

結束語

其實至此,關於本篇元件的初始化、渲染已經介紹完畢,由於程式碼中關於太多後續章節,生命週期、props、state、物件緩衝池、事務等,所以暫時都先略過,後續學習到的時候再回頭查閱。

相關文章