React源碼分析與實(shí)現(xiàn)(一):組件的初始化與渲染

原文鏈接地址:https://github.com/Nealyang 轉(zhuǎn)載請(qǐng)注明出處

前言

戰(zhàn)戰(zhàn)兢兢寫下開篇...也感謝小蘑菇大神以及網(wǎng)上各路大神的博客資料參考~

閱讀源碼的方式有很多種信认,廣度優(yōu)先法卸例、調(diào)用棧調(diào)試法等等产舞,此系列文章道伟,采用基線法役拴,顧名思義,就是以低版本為基線,逐漸了解源碼的演進(jìn)過程和思路。

react最初的設(shè)計(jì)靈感來源于游戲渲染的機(jī)制:當(dāng)數(shù)據(jù)變化時(shí)浇坐,界面僅僅更新變化的部分而形成新的一幀渲染。所以設(shè)計(jì)react的核心就是認(rèn)為UI只是把數(shù)據(jù)通過映射關(guān)系變換成另一種形式的數(shù)據(jù)黔宛,也就是展示方式近刘。傳統(tǒng)上,web架構(gòu)使用模板或者HTML指令構(gòu)造頁面臀晃。react則處理構(gòu)建用戶界面通過將他們份極為virtual dom觉渴,當(dāng)然這也是react的核心,整個(gè)react架構(gòu)的設(shè)計(jì)理念也是為此展開的徽惋。

準(zhǔn)備工作

我們采用基線法去學(xué)習(xí)react源碼案淋,所以目前基于的版本為stable-0.3,后面我們在逐步分析學(xué)習(xí)演變的版本。

clone代碼

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

git checkout 0.3-stable
IMAGE

React源代碼都在src目錄中险绘,src包含了8個(gè)目錄踢京,其主要內(nèi)容描述見下表回右。

目 錄 內(nèi)容
core React 核心類
domUtil Dom操作和CSS操作的相關(guān)工具類
environment 當(dāng)前JS執(zhí)行環(huán)境的基本信息
event React事件機(jī)制的核心類
eventPlugins React事件機(jī)制的事件綁定插件類
test 測試目錄
utils 各種工具類
vendor 可替換模塊存放目錄
IMAGE

我們將該版本編譯后的代碼放到example下,引入到basic/index.html中運(yùn)行調(diào)試漱挚。

組件初始化

使用

這里還是以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方法不能為空旨涝,當(dāng)然,createClass中我們也可以去寫一寫生命周期的鉤子函數(shù)侣背,這里我們暫且省略白华,畢竟目前我們更加的關(guān)注react組建的初始化過程。

同樣贩耐,熟悉react使用方法的人也會(huì)有疑惑了弧腥,怎么實(shí)例代碼中的render最后return的是React.DOM.p(null,message)

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

編譯階段

我們都知道潮太,在js中直接編寫html代碼管搪,或者。铡买。更鲁。jsx語法這樣的AST,在js詞法分析階段就會(huì)拋出異常的奇钞。

對(duì)的澡为,所以我們在編寫react代碼的時(shí)候都會(huì)借助babel去轉(zhuǎn)碼

從babel官網(wǎng)上寫個(gè)例子即可看出:

IMAGE

對(duì)呀!明明人家用的是react.createElement方法景埃,我們怎么出現(xiàn)個(gè)React.DOM.p...

OK媒至,歷史原因:

IMAGE
  • react現(xiàn)在版本中,使用babel-preset-react來編譯jsx谷徙,這個(gè)preset又包含了4個(gè)插件拒啰,其中transform-react-jsx負(fù)責(zé)編譯jsx,調(diào)用了React.createElement函數(shù)生成虛擬組件
  • 在react-0.3里蒂胞,編譯結(jié)果稍稍有些不同图呢,官方給出的示例文件,使用JSXTransformer.js編譯jsx(也就是<script src="../source/JSXTransformer.js"></script>)骗随,對(duì)于native組件和composite組件編譯的方式也不一致蛤织。也就是我們看到的React.DOM.p or ReactComponsiteComponent
    • native組件:編譯成React.DOM.xxx(xxx如div),函數(shù)運(yùn)行返回一個(gè)ReactNativeComponent實(shí)例鸿染。
    • composite組件:編譯成createClass返回的函數(shù)調(diào)用指蚜,函數(shù)運(yùn)行返回一個(gè)ReactCompositeComponent實(shí)例

題外話,不管用什么框架涨椒,到瀏覽器這部分的摊鸡,什么花里胡哨的都不復(fù)存在绽媒。我這就是js、css免猾、html是辕。所以我們這里的ReactCompositeComponent最終其實(shí)還是需要轉(zhuǎn)成原生元素的 。\

組件創(chuàng)建

從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復(fù)合組件的原型函數(shù)
            Constructor.prototype = new ReactCompositeComponentBase();
            Constructor.prototype.constructor = Constructor;
            // 把消費(fèi)者聲明配置spec合并到Constructor.prototype中
            mixSpecIntoComponent(Constructor, spec);
            // 判斷合并后的結(jié)果有沒有render猎提,如果沒有 render获三,拋出一個(gè)異常
            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返回一個(gè)ConvenienceConstructor構(gòu)造函數(shù),構(gòu)造函數(shù)接受props锨苏、children 構(gòu)造函數(shù)的靜態(tài)方法componentConstructor和originalSpec分別指向Constructor和spec疙教。
  • 有種類似于寄生組合式繼承的寫法,Constructor為每一個(gè)組件實(shí)例的原型(var instance = new Constructor(); instance.construct.apply(instance, arguments);)伞租。Constructor原型指向ReactCompositeComponentBase贞谓,又把構(gòu)造器指向Constructor自己。然后把傳入的spec合并到Constructor.prototype中葵诈。判斷合并后的結(jié)果有沒有render裸弦,如果沒有 render,拋出一個(gè)異常

其實(shí)很多人看到這估計(jì)都會(huì)很疑惑作喘,為毛這樣搞烁兰??徊都?直接返回個(gè)構(gòu)造函數(shù)不就可以了嘛沪斟。

其實(shí)react在后面做diff算法的時(shí)候,是采用組件的Constructor來判斷組件是否相同的暇矫。如此可以保證每個(gè)createClass創(chuàng)建出來的組件都是一個(gè)新的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呢李根?說實(shí)話槽奕,我也不知道,然后看了在網(wǎng)上查到相關(guān)信息說道:

上面寫法在大多數(shù)情況下并不會(huì)產(chǎn)生什么問題房轿,但是粤攒,當(dāng)團(tuán)隊(duì)里的人無意中修改錯(cuò)點(diǎn)什么,比如:

Table1.prototype.onClick = null

這樣囱持,所有Table1實(shí)例化的組件夯接,onClick全部為修改后的空值

<Table1 />
<Table1 />

我們知道,js是動(dòng)態(tài)解釋型語言纷妆,函數(shù)可以運(yùn)行時(shí)被隨意篡改盔几。而靜態(tài)編譯語言在運(yùn)行時(shí)期間,函數(shù)不可修改(某些靜態(tài)語言也可以修改)掩幢。所以采用這種方式防御用戶對(duì)代碼的篡改逊拍。

組件實(shí)例化

既然createClass返回的是一個(gè)構(gòu)造函數(shù)上鞠,那么我們就來看看他的實(shí)例化吧

          /**
           * 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;
          },

其實(shí)也就是將props、children掛載到this.props上 以及生命周期的設(shè)置芯丧。這里暫且不說芍阎,因?yàn)槲乙舱诳础SШ恪D茉M圻沁?/p>

這里的

this.props[OWNER] = ReactCurrentOwner.current;

this.props[OWNER]指的是當(dāng)前組件的容器(父)組件實(shí)例

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

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

const Parent = React.createClass({
    render: () => <Children />
})  

這里輸出的就是Parent組件實(shí)例肿轨。

再看看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;
}

可以看出來,在執(zhí)行render前后蕊程,分別設(shè)置了ReactCurrentOwner.current的值椒袍,這樣就能保證render函數(shù)內(nèi)的子組件能賦上當(dāng)前組件的實(shí)例,也就是this藻茂。

組件渲染

我們先撇開事務(wù)驹暑、事件池、生命周期辨赐、diff當(dāng)然也包括fiber 等优俘,先不談,其實(shí)渲染就是將經(jīng)過babel編譯后的掀序,當(dāng)然這里是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版本中叶雹,渲染調(diào)用的是ReactDOM.render方法,這里ReactMount.renderComponent為我們的入口方法换吧。

ReactMount.renderComponent在react初探章節(jié)講過折晦。如果組件渲染過,就更新組件屬性沾瓦,如果組件沒有渲染過满着,掛載組件事件,并把虛擬組件渲染成真實(shí)組件插入container內(nèi)贯莺。通常风喇,我們很少去調(diào)用兩次renderComponent,所以大多數(shù)情況下不會(huì)更新組件屬性而是新創(chuàng)建dom節(jié)點(diǎn)并插入到container中缕探。

ReactComponent.mountComponentIntoNode之內(nèi)開啟了一個(gè)事務(wù)响驴,事務(wù)保證渲染階段不會(huì)有任何事件觸發(fā),并阻斷的componentDidMount事件撕蔼,待執(zhí)行后執(zhí)行等豁鲤,事務(wù)在功能一章我們會(huì)詳細(xì)講解秽誊,這里不細(xì)討論。
ReactComponent._mountComponentIntoNode這個(gè)函數(shù)調(diào)用mountComponent獲得要渲染的innerHTML琳骡,然后更新container的innerHTML锅论。
ReactCompositeComponent.mountComponent是最主要的邏輯方法。這個(gè)函數(shù)內(nèi)處理了react的生命周期以及componentWillComponent和componentDidMount生命周期鉤子函數(shù)楣号,調(diào)用render返回實(shí)際要渲染的內(nèi)容最易,如果內(nèi)容是復(fù)合組件,仍然會(huì)調(diào)用mountComponent炫狱,復(fù)合組件最終一定會(huì)返回原生組件藻懒, 并且最終調(diào)用ReactNativeComponent的mountComponent函數(shù)生成要渲染的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第一個(gè)參數(shù)其實(shí)傳入的是react分配給組件的一個(gè)唯一標(biāo)識(shí)


    IMAGE
    mountComponentIntoNode: function(rootID, container) {
      var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
      transaction.perform(
        this._mountComponentIntoNode,
        this,
        rootID,
        container,
        transaction
      );
      ReactComponent.ReactReconcileTransaction.release(transaction);
    },

源碼中酷含,這里跟事務(wù)扯到了關(guān)系鄙早,其實(shí)我們只要關(guān)注渲染本身,所以這里我們直接看this._mountComponentIntoNode的方法實(shí)現(xiàn)

  • _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

流程的確如上椅亚,作為一個(gè)初探源碼者限番,我當(dāng)然不關(guān)心你到底是在哪innerHTML的,我想知道你是腫么把jsx編譯后的Obj轉(zhuǎn)成HTML的哇~

IMAGE
  • ReactCompositeComponent.mountComponent

這里類變成了ReactCompositeComponent(debugger可以跟蹤每一個(gè)函數(shù))

IMAGE

源碼中的this.mountComponent呀舔,為什么不是調(diào)用ReactComponent.mountComponent呢弥虐?這里主要使用了多重繼承機(jī)制(Mixin,后續(xù)講解)媚赖。

  mountComponent: function(rootID, transaction) {
  // 掛在組件ref(等于當(dāng)前組件實(shí)例)到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躯舔,執(zhí)行校驗(yàn)
    if (this.constructor.propDeclarations) {
      this._assertValidProps(this.props);
    }
    // 為組件聲明時(shí)間綁定this
    if (this.__reactAutoBindMap) {
      this._bindAutoBindMethods();
    }
    //獲取state
    this.state = this.getInitialState ? this.getInitialState() : null;
    this._pendingState = null;

    // 如果組件聲明componentWillMount函數(shù),執(zhí)行并把setState的結(jié)果更新到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隊(duì)列中
    if (this.componentDidMount) {
      transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
    }
    
    // 調(diào)用組件聲明的render函數(shù)粥庄,并返回ReactComponent抽象類實(shí)例(ReactComponsiteComponent或
    // ReactNativeComponent),調(diào)用相應(yīng)的mountComponent函數(shù)
    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);
  },

這個(gè)函數(shù)式VDom中最為重要的函數(shù)豺妓,操作也最為復(fù)雜惜互,執(zhí)行操作大概如下:


IMAGE

如上,很多內(nèi)容跟我們這part有點(diǎn)超綱琳拭。當(dāng)然训堆,后面都會(huì)說道,關(guān)于react的渲染白嘁,其實(shí)我們的工作很簡單坑鱼,不關(guān)于任何,在拿到render的東西后,如何解析鲁沥,其實(shí)就是最后一行代碼: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();
}

代碼稍微多一點(diǎn)呼股,但是工作目標(biāo)很單一,就是為了將描述jsx的obj解析成HTML string画恰。其實(shí)可以參照我上面直接亮出來的自己寫的代碼部分彭谁。

如上,其實(shí)我們已經(jīng)完成了組件的初始化允扇、渲染~

img

好吧缠局,我們一直說的渲染的核心部分還沒有細(xì)說~~~

掛載組件ref到this.refs上,設(shè)置生命周期考润、狀態(tài)和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上掛在當(dāng)前組件,也就是this糊治,實(shí)現(xiàn)如下:

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

上述代碼我刪除了相關(guān)的判斷警告唱矛。

設(shè)置組件生命狀態(tài)

組件的生命狀態(tài)和生命周期鉤子函數(shù)是react的兩個(gè)概念,在react中存在兩種生命周期

  • 主:組件生命周期:_lifeCycleState,用來校驗(yàn)react組件在執(zhí)行函數(shù)時(shí)狀態(tài)值是否正確
  • 輔:復(fù)合組件生命周期:_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

在源碼中使用其只是為了在相應(yīng)的階段觸發(fā)時(shí)候校驗(yàn)馆匿,并且給出錯(cuò)誤提示

    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

復(fù)合組件的生命周期只在一個(gè)地方使用: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會(huì)調(diào)用replaceState ,然后調(diào)用_receivePropsAndState來更新界面

如果組件正處在mounting的過程或者接受props的過程中抑胎,那么將state緩存在_pendingState中兽掰,并不會(huì)更新界面的值方面。

校驗(yàn)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是運(yùn)行時(shí)傳入的屬性唐全。我們可以看到聲明props的屬性值即為checkProp

結(jié)束語

其實(shí)至此衷快,關(guān)于本篇組件的初始化稽亏、渲染已經(jīng)介紹完畢棺牧,由于代碼中關(guān)于太多后續(xù)章節(jié)螃成,生命周期呕臂、props破托、state、對(duì)象緩沖池歧蒋、事務(wù)等土砂,所以暫時(shí)都先略過,后續(xù)學(xué)習(xí)到的時(shí)候再回頭查閱谜洽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萝映,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阐虚,更是在濱河造成了極大的恐慌序臂,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件实束,死亡現(xiàn)場離奇詭異奥秆,居然都是意外死亡逊彭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門吭练,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诫龙,“玉大人,你說我怎么就攤上這事鲫咽∏┰撸” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵分尸,是天一觀的道長锦聊。 經(jīng)常有香客問我,道長箩绍,這世上最難降的妖魔是什么孔庭? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮材蛛,結(jié)果婚禮上圆到,老公的妹妹穿的比我還像新娘。我一直安慰自己卑吭,他們只是感情好芽淡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豆赏,像睡著了一般挣菲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掷邦,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天白胀,我揣著相機(jī)與錄音,去河邊找鬼抚岗。 笑死或杠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宣蔚。 我是一名探鬼主播廷痘,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼件已!你這毒婦竟也來了笋额?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤兄猩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后枢冤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淹真,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了核蘸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡客扎,死狀恐怖祟峦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徙鱼,我是刑警寧澤宅楞,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站袱吆,受9級(jí)特大地震影響厌衙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绞绒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一婶希、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧处铛,春花似錦饲趋、人聲如沸拐揭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堂污。三九已至家肯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盟猖,已是汗流浹背讨衣。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留式镐,地道東北人反镇。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像娘汞,于是被迫代替她去往敵國和親歹茶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容