【React】ReactDOM.render源码分析

xiaoxiao2021-02-27  197

这是一个hello world,你造咩

ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') );

ReactDOM.render()实际调用ReactMount.render()

/** * @param {ReactElement} nextElement 要插入到DOM中的组件 * @param {DOMElement} container 要插入到的容器 * @param {?function} callback 回调 * @return {ReactComponent} Component instance rendered in `container`.返回ReactComponent */ render: function (nextElement, container, callback) { },

可见render方法的第一个参数是一个ReactElement类型的变量,那么<h1>Hello, world!</h1>是如何转成成为ReactElement类型变量的呢?快往下看呀

ReactElement

JSX中创建React元素最终会被babel转译为createElement(type, config, children), babel根据JSX中标签的首字母来判断是原生DOM组件,还是自定义React组件

<div className="wrap" > <span>123</span> <My className='my' content={456} /> </div>

上面这段代码转义完后如下:

React.createElement( type: 'div', props:{ className: 'wrap', }, children: [ React.createElement( type: 'span', props: null, chilren: '123' ), React.createElement( type:My.default//从其他文件中引入的React组件 props: { className: 'my', content: 456 } ) ] )

所以createElement做了什么呢?

/** * Factory method to create a new React element. This no longer adheres to * the class pattern, so do not use new to call it. Also, no instanceof check * will work. Instead test $$typeof field against Symbol.for('react.element') to check * if something is a React Element. * * @param {*} type * @param {*} key * @param {string|object} ref * @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. * @param {*} owner * @param {*} props * @internal */ 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元素节点 */ ReactElement.createElement = function(type, config, children) { var propName; // Reserved names are extracted var props = {}; var key = null; var ref = null; var self = null; var source = null; 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. // 传过来的children被放在了props 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; } // 静态变量defaultProps,属性设置默认值 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, ); };

由此可见,createElement()接收三个参数(type,config,children),做了一些变量初始化,接着调用了ReactElement()方法。 ReactElement()是一个工厂方法,根据传入的参数返回一个element对象,也是我们一直所说的ReactElement,如下

接下来我们来看一看render()做了什么

这是一串函数

告诉自己,我不晕

/** * Mounting is the process of initializing a React component by creating its * representative DOM elements and inserting them into a supplied `container`. * Any prior content inside `container` is destroyed in the process. * * ReactMount.render( * component, * document.getElementById('container') * ); * * <div id="container"> <-- Supplied `container`. * <div data-reactid=".3"> <-- Rendered reactRoot of React * // ... component. * </div> * </div> * * Inside of `container`, the first element rendered is the "reactRoot". */ var ReactMount = { /**入口render方法 * @param {ReactElement} nextElement 要插入到DOM中的组件 * @param {DOMElement} container 要插入到的容器 * @param {?function} callback 回调 * @return {ReactComponent} Component instance rendered in `container`.返回ReactComponent */ render: function (nextElement, container, callback) { return ReactMount._renderSubtreeIntoContainer( null, nextElement, container, callback, ); }, /** * 将ReactElement插入DOM中,并返回ReactElement对应的ReactComponent。 * ReactElement是React元素在内存中的表示形式,可以理解为一个数据类,包含type,key,refs,props等成员变量 * ReactComponent是React元素的操作类,包含mountComponent(), updateComponent()等很多操作组件的方法 */ _renderSubtreeIntoContainer: function ( parentComponent, nextElement, container, callback, ) { callback = callback === undefined ? null : callback; var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement, }); var nextContext = getContextForSubtree(parentComponent); // 获取要插入到的容器的前一次的ReactComponent,这是为了做DOM diff var prevComponent = getTopLevelWrapperInContainer(container); if (prevComponent) { var prevWrappedElement = prevComponent._currentElement; var prevElement = prevWrappedElement.props.child; // shouldUpdateReactComponent方法判断是否需要更新,它只对同一DOM层级,type相同,key(如果有)相同的组件做DOM diff, if (shouldUpdateReactComponent(prevElement, nextElement)) { var publicInst = prevComponent._renderedComponent.getPublicInstance(); var updatedCallback = callback && function () { validateCallback(callback); callback.call(publicInst); }; ReactMount._updateRootComponent( prevComponent, nextWrappedElement, nextContext, container, updatedCallback, ); return publicInst; } else { //直接unmount ReactMount.unmountComponentAtNode(container); } } // 对于ReactDOM.render()调用,prevComponent为null var reactRootElement = getReactRootElementInContainer(container); var containerHasReactMarkup = reactRootElement && !!internalGetID(reactRootElement); var containerHasNonRootReactChild = hasNonRootReactChild(container); var shouldReuseMarkup = containerHasReactMarkup && !prevComponent && !containerHasNonRootReactChild; var component = ReactMount._renderNewRootComponent( nextWrappedElement, container, shouldReuseMarkup, nextContext, callback, )._renderedComponent.getPublicInstance(); return component; }, /** * Render a new component into the DOM. Hooked by hooks! * * @param {ReactElement} nextElement element to render * @param {DOMElement} container container to render into * @param {boolean} shouldReuseMarkup if we should skip the markup insertion * @return {ReactComponent} nextComponent */ _renderNewRootComponent: function ( nextElement, container, shouldReuseMarkup, context, callback, ) { //初始化ReactComponent,根据ReactElement中不同的type字段,创建不同类型的组件对象,即ReactComponent var componentInstance = instantiateReactComponent(nextElement, false); if (callback) { //。。。。 } // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched // according to the current batching strategy. // 处理batchedMountComponentIntoNode方法调用,将ReactComponent插入DOM中 ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context, ); var wrapperID = componentInstance._instance.rootID; instancesByReactRootID[wrapperID] = componentInstance; return componentInstance; }, _mountImageIntoNode: function( markup, container, instance, shouldReuseMarkup, transaction, ) { //如变量名,是否复用markup,ReactDOM.render()调用为false if (shouldReuseMarkup) { var rootElement = getReactRootElementInContainer(container); if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) { ReactDOMComponentTree.precacheNode(instance, rootElement); return; } else { var checksum = rootElement.getAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME, ); rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); var rootMarkup = rootElement.outerHTML; rootElement.setAttribute( ReactMarkupChecksum.CHECKSUM_ATTR_NAME, checksum, ); var normalizedMarkup = markup; } } if (transaction.useCreateElement) { while (container.lastChild) { container.removeChild(container.lastChild); } DOMLazyTree.insertTreeBefore(container, markup, null); } else { // 利用innerHTML将markup插入到container这个DOM元素上 setInnerHTML(container, markup); // 将instance(Virtual DOM)保存到container这个DOM元素的firstChild这个原生节点上 ReactDOMComponentTree.precacheNode(instance, container.firstChild); } }, } /** * Batched mount. 以transaction事务的形式调用mountComponentIntoNode * @param {ReactComponent} componentInstance The instance to mount. * @param {DOMElement} container DOM element to mount into. * @param {boolean} shouldReuseMarkup If true, do not insert markup */ function batchedMountComponentIntoNode( componentInstance, container, shouldReuseMarkup, context, ) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled( /* useCreateElement */ !shouldReuseMarkup, ); transaction.perform( mountComponentIntoNode, null, componentInstance, container, transaction, shouldReuseMarkup, context, ); ReactUpdates.ReactReconcileTransaction.release(transaction); } /** * Mounts this component and inserts it into the DOM. * * @param {ReactComponent} componentInstance The instance to mount. * @param {DOMElement} container DOM element to mount into. * @param {ReactReconcileTransaction} transaction * @param {boolean} shouldReuseMarkup If true, do not insert markup */ function mountComponentIntoNode( wrapperInstance, container, transaction, shouldReuseMarkup, context, ) { //调用对应ReactComponent中的mountComponent方法来渲染组件,返回React组件解析后的HTML var markup = ReactReconciler.mountComponent( wrapperInstance, transaction, null, ReactDOMContainerInfo(wrapperInstance, container), context, 0 /* parentDebugID */, ); wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance; // 将解析出来的HTML插入DOM中 ReactMount._mountImageIntoNode( markup, container, wrapperInstance, shouldReuseMarkup, transaction, ); } /** * Given a ReactNode, create an instance that will actually be mounted. * 根据ReactElement中不同的type字段,创建不同类型的组件对象 * * ReactEmptyComponent.create(), 创建空对象ReactDOMEmptyComponent * ReactNativeComponent.createInternalComponent(), 创建DOM原生对象 ReactDOMComponent * new ReactCompositeComponentWrapper(), 创建React自定义对象ReactCompositeComponent * ReactNativeComponent.createInstanceForText(), 创建文本对象 ReactDOMTextComponent * * @param {ReactNode} node * @param {boolean} shouldHaveDebugID * @return {object} A new instance of the element's constructor. * @protected */ function instantiateReactComponent(node, shouldHaveDebugID) { var instance; if (node === null || node === false) { // 空对象 instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { // 组件对象,包括DOM原生的和React自定义组件 var element = node; var type = element.type; if (typeof element.type === 'string') { // DOM原生对象 instance = ReactHostComponent.createInternalComponent(element); } else if (isInternalComponentType(element.type)) { // This is temporarily available for custom components that are not string // representations. I.e. ART. Once those are updated to use the string // representation, we can drop this code path. instance = new element.type(element); // We renamed this. Allow the old name for compat. :( if (!instance.getHostNode) { instance.getHostNode = instance.getNativeNode; } } else { // React自定义组件 instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { // 文本对象 instance = ReactHostComponent.createInstanceForText(node); } else { // 报error } // 还记得dom diff中的_mountIndex吗? instance._mountIndex = 0; instance._mountImage = null; return instance; }

好吧,我也晕了。。 大体过程如下:

转载请注明原文地址: https://www.6miu.com/read-11776.html

最新回复(0)