源码基于v15.6.2分支
和Vue类似,先通过React.createElement()
生成 VNode Tree,再通过ReactDOM.render()
挂载到真实DOM节点上。
// 函数组件 function functionComponent(props) { return (<h1 {...props}>test</h1>) // 最终生成JSX,JSX由babel解析为React.createElemnet } ReactDOM.render(functionComponent({field: 1}), el) // 类组件 class A extends React.Component { // 类组件必须有render函数。有自身状态State和生命周期。 render() { return <h1 {...props}>test</h1> } } ReactDOM.render(<A />, el)
Copied!
类似Vue的createElement方法,但React的config对象较为简单,都作为props传入,Vue的VDOM基于snabbdom库,传入的config对象限制较多(如:on、atrrs、props)。两者最终都是得到VDOM Tree的数据机构。
babel根据JSX中标签的首字母来判断是原生DOM组件,还是自定义React组件。如果首字母大写,则为React组件。这也是为什么ES6中React组件类名必须大写的原因
<div className="title" ref="example"> <span>123</span> // 原生DOM组件,首字母小写 <ErrorPage title='123456' desc={[]}/> // 自定义组件,首字母大写 </div>
Copied!
// JSX转译后js React.createElement( // type,标签名,原生DOM对象为String 'div', // config,属性 { className: 'title', ref: 'example' }, // children,子元素 React.createElement('span', null, '123'), React.createElement( // type,标签名,React自定义组件的type不为String. // _errorPage2.default为从其他文件中引入的React组件 _errorPage2.default, { title: '123456', desc: [] } ) )
Copied!
// package/react/src/ReactElement.js ReactElement.createElement = function (type, config, children) { var propName; // 初始化参数 var props = {}; var key = null; var ref = null; var self = null; var source = null; // 从config中提取出内容,如ref key props if (config != null) { ref = config.ref === undefined ? null : config.ref; key = config.key === undefined ? null : '' + config.key; self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // 提取出config中的prop,放入props变量中 for (propName in config) { if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } // 处理children,挂到props的children属性下 var childrenLength = arguments.length - 2; if (childrenLength === 1) { // 只有一个参数时,直接挂到children属性下,不是array的方式 props.children = children; } else if (childrenLength > 1) { // 不止一个时,放到array中,然后将array挂到children属性下 var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; } // 取出组件类中的静态变量defaultProps,并给未在JSX中设置值的属性设置默认值 if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } // 返回一个ReactElement对象 return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); };
Copied!
var ReactElement = function (type, key, ref, self, source, owner, props) { var element = { $$typeof: REACT_ELEMENT_TYPE, // 常量Symbol.for('react.element') // ReactElement对象上的四个变量,特别关键 type: type, // 关键的识别类型,自定义组件一般为function,dom元素一般为string(tag) key: key, // VDOM diff需要,提升性能 ref: ref, // 真实DOM的引用 props: props, // 子结构相关信息(有则增加children字段/没有为空)和组件属性(如style) _owner: owner }; return element; }
Copied!
简化源码流程:
ReactDOM.render()
src/renderers/dom/ReactDOM.js
ReactMount.render
--> ReactMount._renderSubtreeIntoContainer
src/renderers/dom/client/ReactMount.js
ReactMount._renderNewRootComponent
instantiateReactComponent
重要,不同的type,创建不同的组件实例(4种)。主要为了方便生成html时,直接调用组件实例的mountComponent。return new Component(type)。下节具体分析。batchedMountComponentIntoNode
--> mountComponentIntoNode
创建事务,插入真实的DOM节点
wrapperInstance.mountComponent
重要 生成组件实例对应的html代码。下节具体分析。ReactMount._mountImageIntoNode
设置contaner的innerHTML// src/renderers/dom/ReactDOM.js var ReactDOM = { render: ReactMount.render, }; // src/renderers/dom/client/ReactMount.js // nextElement即为AST对象(VNode Tree) render: function(nextElement, container, callback) { return ReactMount._renderSubtreeIntoContainer( null, nextElement, container, callback, ); }
Copied!
_renderSubtreeIntoContainer: function( parentComponent, nextElement, container, ) { // 顶层包装 // VNode.type = TopLevelWrapper,TopLevelWrapper是个函数 var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement, }); // 拿到之前的组件,更新时就进行对比 // 初始渲染prevComponent = false var prevComponent = getTopLevelWrapperInContainer(container); if (prevComponent) { // 更新流程 var prevWrappedElement = prevComponent._currentElement; var prevElement = prevWrappedElement.props.child; if (shouldUpdateReactComponent(prevElement, nextElement)) { var publicInst = prevComponent._renderedComponent.getPublicInstance(); ReactMount._updateRootComponent( prevComponent, nextWrappedElement, ); return publicInst; } else { // remove container节点下的所以元素 ReactMount.unmountComponentAtNode(container); // while (container.lastChild) {container.removeChild(container.lastChild);}} } } // 根据AST type,1.创建对应component实例,2. 实例递归生成对应的HTML,3. innerHTML挂载到真实的DOM上 var component = ReactMount._renderNewRootComponent( nextWrappedElement, container, )._renderedComponent.getPublicInstance(); return component; }
Copied!
_renderNewRootComponent: function( nextElement, container, ) { // 创建component实例。instantiateReactComponent.js var componentInstance = instantiateReactComponent(nextElement, false); ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, //回调函数 ); return componentInstance; },
Copied!
function batchedMountComponentIntoNode() { // 包装成事务方式 transaction.perform( mountComponentIntoNode, // 回调 ); }
Copied!
function mountComponentIntoNode() { // 根据不同的组件类型(4种)类型,返回组件对应的HTML(下节详细讲述) // 等同于wrapperInstance.mountComponent var markup = ReactReconciler.mountComponent(wrapperInstance); // 给dom插入innerHTML ReactMount._mountImageIntoNode( markup, container, wrapperInstance ); }
Copied!
_mountImageIntoNode: function( markup, container, instance ) { // 关键:container.innerHTML = markup; setInnerHTML(container, markup); // 将处理好的组件对象存储在缓存中,提高结构更新的速度。 ReactDOMComponentTree.precacheNode(instance, container.firstChild); }
Copied!