閱讀源碼前无蜂,提前了解下作者對一些術(shù)語的定義,有助于更好地理解源碼温学。以下為根據(jù)官方文檔關(guān)于React (Virtual) DOM Terminology描述進(jìn)行翻譯并配上源碼粟害,以更深入地了解這些概念
在React的名詞術(shù)語中,有五種主要的類型需要加以區(qū)別
. ReactElement / ReactElement Factory
. ReactNode
. ReactComponent / ReactComponent Class
React Elements
React中主要的類型是ReactElement茧跋,它有四種屬性: type
慰丛,props
,key
和ref
瘾杭。從以下源碼可以看出它就是一個普通的對象诅病,通過$$typeof
屬性來識別是否是React Element,值為Symbol.for('react.element’)或60103
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
...
return element;
};
你可以通過React.createElement
來創(chuàng)建這些對象粥烁。
var root = React.createElement('div');
我們來看下React.createElement
的源碼贤笆,看看它做了啥?
ReactElement.createElement = function(type, config, children) {
...
if (config != null) {
...
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
...
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
);
};
從源碼中我們可以看到:
-
config
中可以包含保留屬性ref
页徐,key
苏潜,__self
,__source
- React Element的props屬性包含了除保留屬性外的所有
config
的屬性值变勇,children和type(通過React.createClass
創(chuàng)建的類或稱構(gòu)造函數(shù))的defaultProps
的所有屬性值 -
ReactElement.createElement
可以傳不止3個參數(shù),第3個以及后面的參數(shù)都作為child傳進(jìn)來,如:
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
你可以創(chuàng)建多個ReactElement
搀绣,它們之間的父子包含關(guān)系形成了一個樹結(jié)構(gòu)飞袋,將最top的ReactElement
和它將所在的容器(一個普通的DOM element,可以是HTMLElement 或者 SVGElement)傳給ReactDOM.render方法链患,這樣就可以在頁面上渲染出這個組件及其所包含的子組件
ReactElement
不是DOM element
巧鸭,它是DOM element
的一個輕量的,無狀態(tài)的麻捻,不可變的虛擬表示纲仍,就是傳說中的虛擬DOM
ReactDOM.render(root, document.getElementById('example'));
可以通過傳遞一個屬性對象給第2個參數(shù)為組件的DOM element增加屬性,可以通過傳遞子React element對象給第3個參數(shù)為組件DOM element增加子的DOM element
var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
ReactDOM.render(root, document.getElementById('example'));
如果你用JSX贸毕,那么以下代碼跟上面的代碼是等價郑叠,JSX編譯器會將以下JSX語法解析成上面的JS代碼
var root = <ul className="my-list">
<li>Text Content</li>
</ul>;
ReactDOM.render(root, document.getElementById('example'));
看累了嗎?小休息一下再接著看唄C鞴鳌O绺铩!
Factories
ReactElement-factory是一個可以生成特定type的ReactElement對象的工廠函數(shù)摊腋。React內(nèi)置了有用的創(chuàng)建工廠函數(shù)的方法
ReactElement.createFactory = function(type) {
var factory = ReactElement.createElement.bind(null, type);
...
return factory;
};
這里小普及一下Function.prototype.bind函數(shù)沸版,它是用來創(chuàng)建一個新的函數(shù),新的函數(shù)的this關(guān)鍵字指向第1個參數(shù)(如果為null則保持不變)兴蒸,第2, ...個參數(shù)的值作為參數(shù)值按順序傳給原來的函數(shù)视粮。舉個栗子吧
var foo = function(p1, p2) { console.log(this, p1, p2); }
var barContext = {};
var bar = foo.bind(barContext, 'hello'); // bar函數(shù)的this關(guān)鍵字指向了barContext,傳給foo函數(shù)的p1參數(shù)的值為'hello'
bar('daniel') // 相當(dāng)于 foo('hello', 'daniel')橙凳,但兩個函數(shù)this關(guān)鍵字指向不同
有了Factory工廠函數(shù)你就不用每次都敲打React.createElement('div')
了馒铃。- Well done,程序員就應(yīng)該能懶則懶
var div = React.createFactory('div');
var root = div({ className: 'my-div' });
ReactDOM.render(root, document.getElementById('example'));
React本身也內(nèi)置了很多針對一般的HTML標(biāo)簽的Factory方法
var root = React.DOM.ul({ className: 'my-list' },
React.DOM.li(null, 'Text Content')
);
React Nodes
一個React Node可以是以下:
. React Element
. string (亦稱ReactText)
. number (亦稱ReactText)
. 一組React Node (亦稱ReactFragment)
它們已作為其它React Element的屬性的形式(props.children)來表示子元素痕惋,從而創(chuàng)建了一個樹結(jié)構(gòu)的組件
React Components
一個ReactComponent Class
就是一個javascript類(或稱為構(gòu)造函數(shù))区宇,它同時也稱為某個ReactElement的類型(type)
var MyComponent = React.createClass({
render: function() {
...
}
});
當(dāng)這個構(gòu)造函數(shù)被調(diào)用時,返回的是一個至少有render方法的對象值戳,這個對象被稱為ReactComponent
(即ReactComponent Class的實(shí)例
)
var component = new MyComponent(props); // 千萬不要這樣去創(chuàng)建ReactComponent
除了測試议谷,不要自己去調(diào)用構(gòu)造函數(shù),交給React來處理這事就好堕虹。
將ReactComponent Class傳給createElement
方法卧晓,返回一個ReactElement
var element = React.createElement(MyComponent);
或者使用JSX:
var element = <MyComponent />;
當(dāng)這個element傳給ReactDom.render
方法時,React會幫你調(diào)用構(gòu)造函數(shù)并返回ReactComponent
var component = ReactDOM.render(element, document.getElementById('example'));
如果你傳入相同類型的ReactElement
和相同的DOM容器繼續(xù)調(diào)用ReactDOM.render
赴捞,它會一直返回同一個實(shí)例逼裆。這個實(shí)例是有狀態(tài)的
var componentA = ReactDOM.render(<MyComponent />, document.getElementById('example'));
var componentB = ReactDOM.render(<MyComponent />, document.getElementById('example'));
componentA === componentB; // true
從源碼中src/renderers/dom/client/ReactMount.js
來驗(yàn)證這個邏輯。
_renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
...
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
var prevWrappedElement = prevComponent._currentElement;
var prevElement = prevWrappedElement.props;
if (shouldUpdateReactComponent(prevElement, nextElement)) {
var publicInst = prevComponent._renderedComponent.getPublicInstance();
var updatedCallback = callback && function() {
callback.call(publicInst);
};
ReactMount._updateRootComponent(
prevComponent,
nextWrappedElement,
container,
updatedCallback
);
return publicInst;
} else {
...
}
}
...
},
可以看出是通過shouldUpdateReactComponent
方法來判斷是否只更新并返回原來的實(shí)例publicInst
赦政,而shouldUpdateReactComponent
針對ReactElement會判斷type
和key
屬性是否相等
這就是為什么不讓你自己創(chuàng)建ReactComponent的原因胜宇,因?yàn)镽eact作了優(yōu)化的處理耀怜,可不是隨隨便便就New New New哦。
ReactComponent
的render
方法(就是你在聲明一個組件時定義的render方法)會返回另外一個ReactElement桐愉,所以這些Component可以進(jìn)行組合财破。最終會解析成DOM element實(shí)例并插入到document中
正式的類型定義
入口
ReactDOM.render = (ReactElement, HTMLElement | SVGElement) => ReactComponent;
Nodes 和 Elements
type ReactNode = ReactElement | ReactFragment | ReactText;
type ReactElement = ReactComponentElement | ReactDOMElement;
type ReactDOMElement = {
type : string,
props : {
children : ReactNodeList,
className : string,
etc.
},
key : string | boolean | number | null,
ref : string | null
};
type ReactComponentElement<TProps> = {
type : ReactClass<TProps> | ReactFunctionalComponent<TProps>,
props : TProps,
key : string | boolean | number | null,
ref : string | null
};
type ReactFragment = Array<ReactNode | ReactEmpty>;
type ReactNodeList = ReactNode | ReactEmpty;
type ReactText = string | number;
type ReactEmpty = null | undefined | boolean;
Classes and Components
type ReactClass<TProps> = (TProps) => ReactComponent<TProps>;
type ReactComponent<TProps> = {
props : TProps,
render : () => ReactElement
};
type ReactFunctionalComponent<TProps> = (TProps) => ReactElement;
最后,期待吐槽从诲,期待指教W罅 !系洛!
--EOF--