ReactElement源码解析
前言
ReactElement并不像之前所谈的PureComponent和Component那样被频繁的显示使用,但我估计他应该是在react暴露出的api中被调用最为频繁的,关于此看完后面便知。ReactElement中暴露出createElement,createFactory,cloneElement,isValidElement,cloneAndReplaceKey五个方法,总共400来行代码,比较容易。
文章中如有不当之处,欢迎交流指点。react版本16.8.2
。在源码添加的注释在githubreact-source-learn。
jsx与ReactElement
在使用react时我们经常在render方法返回(函数组件的话可能是直接返回)类似下面的代码。
<Wrap> <h1>测试</h1> <List /> <Footer /> </Wrap>
这就是传说中的jsx语法,js并没有这种东西,这种语法最终都会被转换成标准的js。请看下图:
发现这些jsx被转化成了js,每个组件或者html标签转化后都是调用React.createElement(type, config, children)。这里的React.createElement其实就是ReactElement.createElement。由此可以推测,ReactElement暴露的方法是调用最频繁的。
createElement解析
createElement的主要调用如下:
createElement -> ReactElement
当然在dev下还有些其他的调用。
createElement源码如下
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */ // jsx转换后调用的方法 export function createElement(type, config, children) { let propName; // Reserved names are extracted const props = {}; let key = null; let ref = null; let self = null; let 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 // 将config中的数据放到props中, key,ref,__self,__source除外 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生成 const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } if (__DEV__) { if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } // Resolve default props // 复制默认props if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } // 这里不然从props中读key, 和ref, 但是里边事实上就是没有的 if (__DEV__) { if (key || ref) { const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { // displayName: 构造函数名, 或标签名 a , h1 defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } // 就一个普通对象 return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); }
createElement主要做了如下事情:
- 将特殊属性从config取出, 如key,ref,__self,__source
- 将非特殊属性挂到props上,比如上边那个图中的className
- 将第三个及之后的参数挂到props.children上,多个是生成数组,单个是直接挂
- 默认值defaultProps的处理
- 将处理好的数据作为参数调用ReactElement并返回
ReactElement源码如下
// 这个函数做的事非常简单, 就是将传进来的参放到一个对象里边返回 // 其中source, self在生产模式没有返回 // owner 变成了_owner // 开发模式下, 返回了将source, self也挂在了返回的对象上, 变成了_source, _self const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // 一个Symobol或者16进制数, //用于表示ReactElement类型 // 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, }; // 这里边放了self, source if (__DEV__) { // 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, }); // Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改; // 冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、 // 可配置性、可写性,以及不能修改已有属性的值。 // 此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。 // Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。 // Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。 if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
他几乎是没做什么事情的,就是将传入的参数放到一个对象返回,加了一个$$typeof标识ReactElement。其中使用了一个Object.freeze方法,这个方法不太常用,意思是冻结一个对象,使其不能被修改,相关的还有Object.seal,Object.preventExtensions,可以找些文档了解下。
小结下ReactElement.createElement
ReactElement.createElement最终返回的是一个普通的对象,对参数进行了校验,提取等操作。上面为解析dev下的代码,去看一下会发现也是比较有趣的。
createFactory,cloneAndReplaceKey,cloneElement和isValidElement
createFactory
export function createFactory(type) { const factory = createElement.bind(null, type); // Expose the type on the factory and the prototype so that it can be // easily accessed on elements. E.g. `<Foo />.type === Foo`. // This should not be named `constructor` since this may not be the function // that created the element, and it may not even be a constructor. // Legacy hook: remove it factory.type = type; return factory; // 这样 // return function factory(...args) { // return createElement(type, ...args); // } }
这个方法很简单,是对createElement的一个柯里化的操作。
cloneAndReplaceKey
// 克隆reactElement并将key改为新key export function cloneAndReplaceKey(oldElement, newKey) { const newElement = ReactElement( oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props, ); return newElement; }
从旧的ReactElement对象生成一个新的,将key属性替换成新的
isValidElement
/** * Verifies the object is a ReactElement. * See https://reactjs.org/docs/react-api.html#isvalidelement * @param {?object} object * @return {boolean} True if `object` is a ReactElement. * @final */ export function isValidElement(object) { // 还是很严谨的 return ( typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE ); }
判断一个值是不是ReactElement,使用了创建时挂上去的$$typeof
cloneElement
// 和createElement基本相同 export function cloneElement(element, config, children) { invariant( !(element === null || element === undefined), 'React.cloneElement(...): The argument must be a React element, but you passed %s.', element, ); let propName; // Original props are copied const props = Object.assign({}, element.props); // Reserved names are extracted let key = element.key; let ref = element.ref; // Self is preserved since the owner is preserved. const self = element._self; // Source is preserved since cloneElement is unlikely to be targeted by a // transpiler, and the original source is probably a better indicator of the // true owner. const source = element._source; // Owner will be preserved, unless ref is overridden let owner = element._owner; if (config != null) { if (hasValidRef(config)) { // Silently steal the ref from the parent. ref = config.ref; owner = ReactCurrentOwner.current; } if (hasValidKey(config)) { key = '' + config.key; } // Remaining properties override existing props let defaultProps; if (element.type && element.type.defaultProps) { defaultProps = element.type.defaultProps; } for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { if (config[propName] === undefined && defaultProps !== undefined) { // Resolve default props props[propName] = defaultProps[propName]; } else { props[propName] = config[propName]; } } } } // Children can be more than one argument, and those are transferred onto // the newly allocated props object. const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; } return ReactElement(element.type, key, ref, self, source, owner, props); }
这段代码和createElement非常相似,不同之处在于他是返回第一个参数ReactElement的一个副本。他的key,ref等属性和提供的需要被克隆的ReactElement的相同,props也是原来的props,但是可以传入config修改。
/packages/react.js小结
至此,/packages/react.js总的最最重要的东西已经分析完了,关于hooks等其他内容就像不分析了。这里边的代码其实并没有做什神奇的事情,ReactElement只是创建和操作普通对象,Component和PureComponent只是定义了两个简单的构造函数,定义了几个方法,其中比较重要的应该是updater,但是到目前为止还没有看到他的身影。这些东西都不涉及dom操作,是平台无关的。
这里代码都比较好理解,后面就将进入深水区了,要开始研究ReactDOM里边的render了。加油!