本文主要学习React.createElement()API的应用。在学习之前,我们需要知道React的几个基础核心概念,React Element,React Component,React Instance不熟悉的童鞋推荐阅读下文。
React Components, Elements, and Instances。
我们都知道JSX是React中的一种标签语法,它是一个语法糖,主要用来取代React.createElement()产生React Element。但是浏览器呢不支持JSX,因此我们需要一个工具Babel来进行翻译。因此Babel就是把JSX转为浏览器相兼容的格式。
我们可以去Babel官网提供的环境写几个DEMO看看它是怎么转换的。源码位置在createelement。
// ReactSymbols.js // The Symbol used to tag the ReactElement-like types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. const hasSymbol = typeof Symbol === 'function' && Symbol.for; export const REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7; 复制代码
// ReactElement.js /** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement * type: 节点类型,可以是原生DOM节点或者React Component。React Component的type需要大写,否则当作普通DOM处理。 * config: 对应的属性集合。 * children: 子节点。 */ 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) { // 找出对应ref if (hasValidRef(config)) { ref = config.ref; } // 找出对应key 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 // 遍历取出propName挂载props 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. // 子节点长度 const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { // 找出对应所有子节点children, 转为数组挂载props 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 // defaultProps 可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为 null 的情况 // 为props添加defaultProps if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { // 如果 props.xxx 被设置为 null,则它将保持为 null if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { if (key || ref) { const displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); } /** * 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 {*} props * @param {*} key * @param {string|object} ref * @param {*} owner * @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 */ const ReactElement = function(type, key, ref, self, source, owner, props) { const element = { // This tag allows us to uniquely identify this as a React Element // 通过Symbol作为独一无二标识符 Symbol.for进行复用 $$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, }; 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, }); if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; }; 复制代码
我们将下面代码在React中执行看看打印出来的React Element是什么样子。
function Comp() { return React.createElement("button", null, "btn"); } const a = React.createElement("Comp", { className: "demo", attr: "12" }, "inner", React.createElement("p", { id: "p" }, "p"), React.createElement("span", { id: "span" }, "span")); console.log(a) 复制代码
我们可以对比下真正打出的React Element。
现在对React.createElemt有一个粗浅的认识,至于如何将其转为为真正的DOM往后看。