閱讀源碼前,提前了解下作者對一些術語的定義,有助于更好地理解源碼。以下為根據官方文檔關于React (Virtual) DOM Terminology描述進行翻譯并配上源碼,以更深入地了解這些概念
在React的名詞術語中,有五種主要的類型需要加以區別
. 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
來創建這些對象。
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
創建的類或稱構造函數)的defaultProps
的所有屬性值 -
ReactElement.createElement
可以傳不止3個參數,第3個以及后面的參數都作為child傳進來,如:
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
你可以創建多個ReactElement
,它們之間的父子包含關系形成了一個樹結構,將最top的ReactElement
和它將所在的容器(一個普通的DOM element,可以是HTMLElement 或者 SVGElement)傳給ReactDOM.render方法,這樣就可以在頁面上渲染出這個組件及其所包含的子組件
ReactElement
不是DOM element
,它是DOM element
的一個輕量的,無狀態的,不可變的虛擬表示,就是傳說中的虛擬DOM
ReactDOM.render(root, document.getElementById('example'));
可以通過傳遞一個屬性對象給第2個參數為組件的DOM element增加屬性,可以通過傳遞子React element對象給第3個參數為組件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'));
看累了嗎?小休息一下再接著看唄!!!
Factories
ReactElement-factory是一個可以生成特定type的ReactElement對象的工廠函數。React內置了有用的創建工廠函數的方法
ReactElement.createFactory = function(type) {
var factory = ReactElement.createElement.bind(null, type);
...
return factory;
};
這里小普及一下Function.prototype.bind函數,它是用來創建一個新的函數,新的函數的this關鍵字指向第1個參數(如果為null則保持不變),第2, ...個參數的值作為參數值按順序傳給原來的函數。舉個栗子吧
var foo = function(p1, p2) { console.log(this, p1, p2); }
var barContext = {};
var bar = foo.bind(barContext, 'hello'); // bar函數的this關鍵字指向了barContext,傳給foo函數的p1參數的值為'hello'
bar('daniel') // 相當于 foo('hello', 'daniel'),但兩個函數this關鍵字指向不同
有了Factory工廠函數你就不用每次都敲打React.createElement('div')
了。- Well done,程序員就應該能懶則懶
var div = React.createFactory('div');
var root = div({ className: 'my-div' });
ReactDOM.render(root, document.getElementById('example'));
React本身也內置了很多針對一般的HTML標簽的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)來表示子元素,從而創建了一個樹結構的組件
React Components
一個ReactComponent Class
就是一個javascript類(或稱為構造函數),它同時也稱為某個ReactElement的類型(type)
var MyComponent = React.createClass({
render: function() {
...
}
});
當這個構造函數被調用時,返回的是一個至少有render方法的對象,這個對象被稱為ReactComponent
(即ReactComponent Class的實例
)
var component = new MyComponent(props); // 千萬不要這樣去創建ReactComponent
除了測試,不要自己去調用構造函數,交給React來處理這事就好。
將ReactComponent Class傳給createElement
方法,返回一個ReactElement
var element = React.createElement(MyComponent);
或者使用JSX:
var element = <MyComponent />;
當這個element傳給ReactDom.render
方法時,React會幫你調用構造函數并返回ReactComponent
var component = ReactDOM.render(element, document.getElementById('example'));
如果你傳入相同類型的ReactElement
和相同的DOM容器繼續調用ReactDOM.render
,它會一直返回同一個實例。這個實例是有狀態的
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
來驗證這個邏輯。
_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
方法來判斷是否只更新并返回原來的實例publicInst
,而shouldUpdateReactComponent
針對ReactElement會判斷type
和key
屬性是否相等
這就是為什么不讓你自己創建ReactComponent的原因,因為React作了優化的處理,可不是隨隨便便就New New New哦。
ReactComponent
的render
方法(就是你在聲明一個組件時定義的render方法)會返回另外一個ReactElement,所以這些Component可以進行組合。最終會解析成DOM element實例并插入到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;
最后,期待吐槽,期待指教!!!
--EOF--