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