vlambda博客
学习文章列表

通过源码学习React.createElement


概述

本文主要学习React.createElement()API的应用。在学习之前,我们需要知道React的几个基础核心概念,React Element,React Component,React Instance不熟悉的童鞋推荐阅读下文。

React Components, Elements, and Instances[1]

Babel与JSX

我们都知道JSX是React中的一种标签语法,它是一个语法糖,主要用来取代React.createElement()产生React Element。但是浏览器呢不支持JSX,因此我们需要一个工具Babel来进行翻译。因此Babel就是把JSX转为浏览器相兼容的格式。

我们可以去Babel官网提供的环境写几个DEMO看看它是怎么转换的。通过源码学习React.createElement通过源码学习React.createElement通过源码学习React.createElement

React.createElement

源码位置在createelement[2]

// 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;};

demo

我们将下面代码在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往后看。

References

[1] React Components, Elements, and Instances: https://medium.com/@dan_abramov/react-components-elements-and-instances-90800811f8ca
[2] createelement: https://zh-hans.reactjs.org/docs/react-api.html#createelement