1. 程式人生 > 實用技巧 >轉-react原始碼分析-初探React原始碼

轉-react原始碼分析-初探React原始碼

轉載於:https://www.yuque.com/ant-h5/sourcecode/klduug
作者: ant-h5, 螞蟻金服前端團隊
轉載方式: 手打
轉載者: lemon-Xu

3.初探React原始碼

課前思考題

  • jsx在react中是如何處理的,為什麼xml元素可以放在js檔案內?
  • renderComponent到底做了什麼?

基本示例

在React原始碼目錄下,examples檔案中包含了各種React例項程式碼。其中basic資料夾下,給出了一個React最簡單的例項:

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);

這段程式碼實現了一個自動更新React執行時間的計時器可以算是React框架的Hello Demo。在這個示例程式碼中,引出了兩個非常非常重要的函式,分別是React.createClass和React.renderComponent。

renderComponent

React.renderComponent通常是中各react程式渲染的入口(對應到15.0.0以及往後的版本,就是ReactDOM.render)。
renderComponent介面引出了React框架的渲染機制及其詳細過程。

renderComponent(component,container)負責將一個component例項渲染到給定的container中。React框架在進行渲染時,會盡可能地複用現有的DOM節點,因此,React會先判斷,當前的container是否存在一個與子對應的、已經渲染過的component,具體的查詢過程暫且略過。

如果React並沒有找到某個已有的元件例項與container有關聯,它就會將component與container的關聯資訊儲存下來,以待後續查詢,同時呼叫mountComonentIntoNode,將component掛載到container上。

如果React找到了container關聯的元件例項,則會執行一個更新流程,使用component的屬性資訊,來更新查詢到的react元件例項。這一過程的簡圖如下所示。

/src/core/ReactMount.js


/** * Renders a React component into the DOM in the supplied `container`. * * If the React component was previously rendered into `container`, this will * perform an update on it and only mutate the DOM as necessary to reflect the * latest React component. * * @param {ReactComponent} nextComponent Component instance to render. * @param {DOMElement} container DOM element to render into. * @return {ReactComponent} Component instance rendered in `container`. */renderComponent: function(nextComponent, container) {
  // 獲得歷史快取,如果之前有過renderComponent記錄,把歷史id記錄在instanceByReactRootID變數中  var prevComponent = instanceByReactRootID[getReactRootID(container)];
  if (prevComponent) {
    if (prevComponent.constructor === nextComponent.constructor) {
      var nextProps = nextComponent.props;
      // 保持滾動條不變      ReactMount.scrollMonitor(container, function() {
        // 更新屬性        prevComponent.replaceProps(nextProps);
      });
      return prevComponent;
    } else {
      // 解除安裝之前的元件      ReactMount.unmountAndReleaseReactRootNode(container);
    }
  }
  // 掛載事件  ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
  // 把歷史id記錄在instanceByReactRootID變數中  var reactRootID = ReactMount.registerContainer(container);
  instanceByReactRootID[reactRootID] = nextComponent;
  // 呼叫ReactComponent的createClass的返回結果  nextComponent.mountComponentIntoNode(reactRootID, container);
  return nextComponent;
}

jsx編譯

這個函式第一個引數nextComponent是什麼呢?帶著這個問題,我們打斷點看一下jsx編譯後的結果是什麼。

編譯前:

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 <div>{message}</div>;  }
});
var start = new Date().getTime();
setInterval(function() {
  debugger  React.renderComponent(
    <ExampleApplication elapsed={new Date().getTime() - start} />,
    document.getElementById('container')
  );
}, 50);

編譯後:

通過斷點可以發現:

// 編譯成 -> React.DOM.div(null, message)
<div>{message}</div> 
// 編譯成 -> ExampleApplication( {elapsed:new Date().getTime() - start}, null ),
<ExampleApplication elapsed={new Date().getTime() - start} />

我們通過Object.getPrototypeOf分別打印出來兩個物件的原型物件

可以很明顯看出,對於兩種元件,分別是

  • 複合元件(React元件),編譯後繼承ReactCompositeComponentBase的原型物件(prototype)
  • 原生元件(如div),編譯後繼承ReactNativeComponent的原型物件(prototype)