原文連接地址:github.com/Nealyang 轉載請註明出處css
戰戰兢兢寫下開篇...也感謝小蘑菇大神以及網上各路大神的博客資料參考~html
閱讀源碼的方式有不少種,廣度優先法、調用棧調試法等等,此係列文章,採用基線法,顧名思義,就是以低版本爲基線,逐漸瞭解源碼的演進過程和思路。node
react最初的設計靈感來源於遊戲渲染的機制:當數據變化時,界面僅僅更新變化的部分而造成新的一幀渲染。因此設計react的核心就是認爲UI只是把數據經過映射關係變換成另外一種形式的數據,也就是展現方式。傳統上,web架構使用模板或者HTML指令構造頁面。react則處理構建用戶界面經過將他們份極爲virtual dom,固然這也是react的核心,整個react架構的設計理念也是爲此展開的。react
咱們採用基線法去學習react源碼,因此目前基於的版本爲stable-0.3,後面咱們在逐步分析學習演變的版本。git
git clone https://github.com/facebook/react.git
git checkout 0.3-stable
複製代碼
React源代碼都在src目錄中,src包含了8個目錄,其主要內容描述見下表。github
目 錄 | 內容 |
---|---|
core | React 核心類 |
domUtil | Dom操做和CSS操做的相關工具類 |
environment | 當前JS執行環境的基本信息 |
event | React事件機制的核心類 |
eventPlugins | React事件機制的事件綁定插件類 |
test | 測試目錄 |
utils | 各類工具類 |
vendor | 可替換模塊存放目錄 |
咱們將該版本編譯後的代碼放到example下,引入到basic/index.html中運行調試。web
這裏仍是以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官網上寫個例子便可看出:
對呀!明明人家用的是react.createElement方法,咱們怎麼出現個React.DOM.p...
OK,歷史緣由:
<script src="../source/JSXTransformer.js"></script>
),對於native組件和composite組件編譯的方式也不一致。也就是咱們看到的React.DOM.p or ReactComponsiteComponent
題外話,無論用什麼框架,到瀏覽器這部分的,什麼花裏胡哨的都不復存在。我這就是js、css、html。因此咱們這裏的ReactCompositeComponent最終其實仍是須要轉成原生元素的 。\
從React.js中咱們能夠找到createClass的出處:
"use strict";
var ReactCompositeComponent = require('ReactCompositeComponent');
...
var React = {
...
createClass: ReactCompositeComponent.createClass,
...
};
module.exports = React;
複製代碼
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;
},
複製代碼
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)
})
}
複製代碼
是的,就這樣
OK,回到源碼~
在咱們目前使用的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。
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: function(rootID, container) {
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._mountComponentIntoNode,
this,
rootID,
container,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
},
複製代碼
源碼中,這裏跟事務扯到了關係,其實咱們只要關注渲染自己,因此這裏咱們直接看this._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);
},
複製代碼
上述代碼流程大概以下:
流程的確如上,做爲一個初探源碼者,我固然不關心你究竟是在哪innerHTML的,我想知道你是腫麼把jsx編譯後的Obj轉成HTML的哇~
這裏類變成了ReactCompositeComponent(debugger能夠跟蹤每個函數)
源碼中的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中最爲重要的函數,操做也最爲複雜,執行操做大概以下:
如上,不少內容跟咱們這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。其實能夠參照我上面直接亮出來的本身寫的代碼部分。
如上,其實咱們已經完成了組件的初始化、渲染~
好吧,咱們一直說的渲染的核心部分尚未細說~~~
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中存在兩種生命週期
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;
},
複製代碼
複合組件的生命週期只在一個地方使用: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中,並不會更新界面的值。
_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、對象緩衝池、事務等,因此暫時都先略過,後續學習到的時候再回頭查閱。