- createElement是执行了new么?
- 为什么会有React 和 ReactDOM?
标重点:版本是17.0.0-alpha.0
先来看一段代码
class ShoppingList extends React.Component {
render() {
return (
<div className="shopping-list">
<h1>Shopping List for {this.props.name}</h1>
<List />
</div>
);
}
}
class List extends React.Component{
constructor(props) {
super(props);
this.state = {
value: ['Instagram', 'WhatsApp','Oculus' ],
};
}
render() {
const lis = [];
this.state.value.forEach(item => {
lis.push(<li>{item}</li>)
})
return (
<ul>
{lis}
</ul>
);
}
}
ReactDOM.render(
<div>
<ShoppingList name="Mark" />
<h1>Hello World!</h1></div>,
document.getElementById('container')
);
相信写过react代码的大家都不会读不懂这段代码;
那么,这段代码到底是怎么执行的呢?
首先,如果直接把这段代码放到浏览器里,浏览器能读懂么?
显然是不能,连新手前端工程师都应该明白浏览器看不懂,这也明显不是我们常见的可以执行的代码,那么浏览器真正能看懂的是什么呢?
看经过babel翻译过后的这段代码:
var ShoppingList = function (_React$Component) {
_inherits(ShoppingList, _React$Component);
function ShoppingList() {
_classCallCheck(this, ShoppingList);
return _possibleConstructorReturn(this, (ShoppingList.__proto__ || Object.getPrototypeOf(ShoppingList)).apply(this, arguments));
}
_createClass(ShoppingList, [{
key: 'render',
value: function render() {
return React.createElement(
'div',
{ className: 'shopping-list' },
React.createElement(
'h1',
null,
'Shopping List for ',
this.props.name
),
React.createElement(List, null)
);
}
}]);
return ShoppingList;
}(React.Component);
var List = function (_React$Component2) {
_inherits(List, _React$Component2);
function List(props) {
_classCallCheck(this, List);
debugger;
var _this2 = _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).call(this, props));
_this2.state = {
value: ['Instagram', 'WhatsApp', 'Oculus']
};
return _this2;
}
_createClass(List, [{
key: 'render',
value: function render() {
var lis = [];
this.state.value.forEach(function (item) {
lis.push(React.createElement(
'li',
null,
item
));
});
return React.createElement(
'ul',
null,
lis
);
}
}]);
return List;
}(React.Component);
ReactDOM.render(React.createElement(
'div',
null,
React.createElement(ShoppingList, { name: 'Mark' }),
React.createElement(
'h1',
null,
'Hello World!'
)
), document.getElementById('container'));
我们的class都被翻译成了function,说明class本身也就是语法糖而已,那么其中的属性继承什么的就不多说了;
而JSX语法表示的组件都显然被转为React.createElement,而且可以见到子组件是在createElement的第三个参数开始往后列;
render则执行的是ReactDOM.render;
我们知道React的渲染可以在很多环境中,那么有ReactDOM也不奇怪的;那么就看看React里做了什么,ReactDOM又做了什么;
React很小,ReactDOM很大
看到build后的代码之后
(如果想知道怎么获得build后的非压缩代码就看官网,一般开源的代码都会告诉你如何去contribute,[how-to-contribute]https://reactjs.org/docs/how-to-contribute.html,当你读完这个之后就怎么知道把一个大型的库看起来很没有头绪的代码最后打包成能执行的整体的办法)。
我们神奇的发现:
React的development非压缩的代码只有3668行;
而ReactDOM则达到了27335行;
那么上文中的React.createElement都干了啥呢??
React的暴露API
[Top API]https://reactjs.org/docs/react-api.html#reactmemo
React.createElement
createElement到底做了什么呢?
简单来说,返回了ReactElement函数的执行结果:
function createElement(type, config, children) {
// 中间省略一大段
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
当然除此之外,中间省略的那一大段还做了:
- 做了各种的类型检查;
- 把key和ref都从config里面给提出来,单独列出来;
- 建了一个新的props对象,把props key都挂上去,把default值都附上去先;
- 做了对props上的key和ref的访问限制;
- 处理了一下children,如果有多个则形成一个数组,单个的话就一个对象,挂到props上;
最后就把整理好的参数传递给了function ReactElment;
ReactElement
- 这是一个工厂函数。用来创造React element,没有实用new来构造,所以并不支持instanceof的检查;
- 但会增加一个
&&typeof
属性来检测是否是react element ,REACT_ELEMENT_TYPE = symbolFor('react.element')
;
直接贴上源码吧:
/**
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @internal
*/
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
// This tag allows 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
};
{
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {}; // To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false
}); // self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self
}); // Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
这就结束了? React.createElment真的就到此就结束了,没有想象中的复杂;
那React在这里还做了什么呢?
我们看到代码里
React.createElement(ShoppingList, { name: 'Mark' })
,
ShoppingList
是什么?可以看到上面的代码ShoppingList是一个自执行函数,最后的返回还是一个function
,这个function
继承了React.Component
,我们可以看出其实在整个createElement
执行过后,其实并没有去new ShopingList
,只是把这个function挂在了 react element
一个属性上,其他什么都没有去做;那么这个new一定不是在React这方执行的;那么只能是在ReactDOM.render这里面才做出的;
还是先来看看React.Component这个基类到底有什么吧;
React.Component
从构造函数看来,props context refs都比较正常;那么涉及到更新的可能就是这个updater了;
构造函数:
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
里面有句很重要
注释:
We initialize the default updater but the real one gets injected by the renderer
updater这里使用的是默认的值,但是真正的updater是render给的;
那么updater是做什么的呢?
那么涉及到react更新最重要的方法是什么呢?肯定是setState
了;
那么我们看看目前挂载在现在这个函数上的setState是怎么写的:
Component.prototype.setState = function (partialState, callback) {
if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
{
throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );
}
}
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
原来如此,那其实setState的执行就是要靠updater来做;而默认ReactNoopUpdateQueue里的enqueueSetState方法是什么呢?
enqueueSetState: function (publicInstance, partialState, callback, callerName) {
warnNoop(publicInstance, 'setState');
}
可以看到只是做了一个warning的判断,没有真正执行真正的逻辑;
的那么是ReactDOM会覆盖基类方法么?ReactDOM的render执行到底怎么执行的呢,请听下回分解吧;
附录React暴露出来的一些方法和属性,感兴趣的可以继续去看看:
exports.Children = Children;
exports.Component = Component;
exports.PureComponent = PureComponent;
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactSharedInternals$1;
exports.cloneElement = cloneElement$1;
exports.createContext = createContext;
exports.createElement = createElement$1;
exports.createFactory = createFactory;
exports.createRef = createRef;
exports.forwardRef = forwardRef;
exports.isValidElement = isValidElement;
exports.lazy = lazy;
exports.memo = memo;
exports.unstable_block = block;
exports.unstable_createMutableSource = createMutableSource;
exports.unstable_startTransition = startTransition;
exports.unstable_useDeferredValue = useDeferredValue;
exports.unstable_useMutableSource = useMutableSource;
exports.unstable_useOpaqueIdentifier = useOpaqueIdentifier;
exports.unstable_useTransition = useTransition;
exports.useCallback = useCallback;
exports.useContext = useContext;
exports.useDebugValue = useDebugValue;
exports.useEffect = useEffect;
exports.useImperativeHandle = useImperativeHandle;
exports.useLayoutEffect = useLayoutEffect;
exports.useMemo = useMemo;
exports.useReducer = useReducer;
exports.useRef = useRef;
exports.useState = useState;
exports.version = ReactVersion;