阅读源码前,提前了解下作者对一些术语的定义,有助于更好地理解源码。以下为根据官方文档关于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--