通过源码学习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
源码位置在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 extractedconst props = {};let key = null;let ref = null;let self = null;let source = null;if (config != null) {// 找出对应refif (hasValidRef(config)) {ref = config.ref;}// 找出对应keyif (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挂载propsfor (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, 转为数组挂载propsconst 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添加defaultPropsif (type && type.defaultProps) {const defaultProps = type.defaultProps;for (propName in defaultProps) {// 如果 props.xxx 被设置为 null,则它将保持为 nullif (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
