React源码 ReactElement
我们的JSX里面标签,属性,内容都会传递到React.createElement()这个方法里面。那么这个方法他到底有什么意义以及他的返回,我们叫他ReactElement。他到底有什么样的作用
/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import ReactVersion from 'shared/ReactVersion'; import { REACT_CONCURRENT_MODE_TYPE, REACT_FRAGMENT_TYPE, REACT_PROFILER_TYPE, REACT_STRICT_MODE_TYPE, REACT_SUSPENSE_TYPE, } from 'shared/ReactSymbols'; import {Component, PureComponent} from './ReactBaseClasses'; import {createRef} from './ReactCreateRef'; import {forEach, map, count, toArray, only} from './ReactChildren'; import { createElement, createFactory, cloneElement, isValidElement, } from './ReactElement'; import {createContext} from './ReactContext'; import {lazy} from './ReactLazy'; import forwardRef from './forwardRef'; import memo from './memo'; import { createElementWithValidation, createFactoryWithValidation, cloneElementWithValidation, } from './ReactElementValidator'; import ReactSharedInternals from './ReactSharedInternals'; import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags'; const React = { Children: { map, forEach, count, toArray, only, }, createRef, Component, PureComponent, createContext, forwardRef, lazy, memo, Fragment: REACT_FRAGMENT_TYPE, StrictMode: REACT_STRICT_MODE_TYPE, Suspense: REACT_SUSPENSE_TYPE, createElement: __DEV__ ? createElementWithValidation : createElement, cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, createFactory: __DEV__ ? createFactoryWithValidation : createFactory, isValidElement: isValidElement, version: ReactVersion, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals, }; if (enableStableConcurrentModeAPIs) { React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE; React.Profiler = REACT_PROFILER_TYPE; } else { React.unstable_ConcurrentMode = REACT_CONCURRENT_MODE_TYPE; React.unstable_Profiler = REACT_PROFILER_TYPE; } export default React;
这就是React.js的源码,我们看这个文件,里面非常简单,上面import一堆,import进来之后干嘛呢?声明了一个React对象。这个对象上面就是我们在外部使用React的时候他给我们提供的api都在这个对象里面进行定义,最后通过export default React。提供给外部使用。这里我们看的是ReactElement。我们看到上面有
import {
createElement,
createFactory,
cloneElement,
isValidElement,
} from './ReactElement';
我们对应的找到ReactElement.js这个文件。
/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import invariant from 'shared/invariant'; import warningWithoutStack from 'shared/warningWithoutStack'; import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import ReactCurrentOwner from './ReactCurrentOwner'; const hasOwnProperty = Object.prototype.hasOwnProperty; const RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true, }; let specialPropKeyWarningShown, specialPropRefWarningShown; function hasValidRef(config) { if (__DEV__) { if (hasOwnProperty.call(config, 'ref')) { const getter = Object.getOwnPropertyDescriptor(config, 'ref').get; if (getter && getter.isReactWarning) { return false; } } } return config.ref !== undefined; } function hasValidKey(config) { if (__DEV__) { if (hasOwnProperty.call(config, 'key')) { const getter = Object.getOwnPropertyDescriptor(config, 'key').get; if (getter && getter.isReactWarning) { return false; } } } return config.key !== undefined; } function defineKeyPropWarningGetter(props, displayName) { const warnAboutAccessingKey = function() { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; warningWithoutStack( false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName, ); } }; warnAboutAccessingKey.isReactWarning = true; Object.defineProperty(props, 'key', { get: warnAboutAccessingKey, configurable: true, }); } function defineRefPropWarningGetter(props, displayName) { const warnAboutAccessingRef = function() { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warningWithoutStack( false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName, ); } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, 'ref', { get: warnAboutAccessingRef, configurable: true, }); } /** * 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 {*} key * @param {string|object} ref * @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. * @param {*} owner * @param {*} props * @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 $$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; }; /** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */ 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 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) { 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 if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { 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, ); } /** * Return a function that produces ReactElements of a given type. * See https://reactjs.org/docs/react-api.html#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; } export function cloneAndReplaceKey(oldElement, newKey) { const newElement = ReactElement( oldElement.type, newKey, oldElement.ref, oldElement._self, oldElement._source, oldElement._owner, oldElement.props, ); return newElement; } /** * Clone and return a new ReactElement using element as the starting point. * See https://reactjs.org/docs/react-api.html#cloneelement */ 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); } /** * 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 ); }
在这个文件里面我们先找到 createElement 这个方法。
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */ 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 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) { 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 if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { 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, ); }
我们看到这里接受三个参数。这个就是 JSX 转 JS ,React.createElement() 的三个参数。
type 就是我们的节点类型,如果是原生的节点,那么这个 type 就是个字符串。如果是我们自己声明的组件,他就是一个 class Component 或者是一个 function 的 Component。当然还有其他的情况,比如说我们可以使用 React 原生的一些组件。比如 Fragment ,StrictMode,Suspense 。这些都是 React 提供给我们的原生组件。他默认就只是一个 Symbol 。他没有任何其他的功能,没有任何其他的含义。就是一个标识。这个后面还会继续讲到,这里就稍微提一下。
config 就是我们写在这个 JSX 标签上面的所有的 attrs 。他们都会变成 key value 的形式存到这个 config 对象里面。我们要从这个对象里面删选出我们认为是真正的 properties 的内容,以及还有特殊的,比如 key, ref 这种特殊的属性。
children 就是我们标签中间放置的内容,比如他是一个子标签,或者直接是个文字。这些都是在 children 里面的。
接下来我们看一下,他是如何创建这个 ReactElement 的。
这个方法首先声明了一堆的变量,然后继续,先判断有没有 config 。找到 hasValidRef, hasValidKey 有没有合理的 ref,有没有合理的 key。然后都把他读到一个单独的变量里面。self source 这两个不是特别的重要。一般不会接触 __self,__source 这样的属性。就不看了。接下来一个 for 循环, 对剩下的 props 进行一个处理,怎么处理呢,就是判断一下他是否是内建的 props 。如果不是的话,那么他就会放到一个拼接的 props 对象里面。如果是内建的 props, 就不放进去了,因为他不属于正常的 props 的范畴,然后继续找这个内建的props。他是个什么东西
const RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true, };
ReactElement.js 文件搜索 RESERVED_PROPS 。得到如上的定义。我们可以看到非正常的 props 就是 key , ref , __self , __source。这几个是不可能出现的,因为在前面就已经处理掉了。把所有 props 属性拿出来放到 props 这个对象里面。
接下来,他会处理一个 children 。
// 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]; } if (__DEV__) { if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; }
还有一个 defaultProps 的处理。
// Resolve default props if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } }
Comp.defaultProps = { value: 1 }
然后这个组件在被别人使用的时候,如果没有传 value 的时候。那么这个1就会作为 this.props.value 的值。
这里面的处理就是当父组件没有给子组件传递参数的时候没有传递,undefined。就会把默认值赋值给 props 。如果是有值的就不设置了。到这里,要处理的就处理完了
最终返回
return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, );
那么 ReactElement 是什么呢。
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, // 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; };
ReactElement 虽然是大写来做,但是并没有写成 class ReactElement 的方式来做。他还是一个方法的使用方式。他最终会返回 element 这个 Object 。这个 Object 他最主要的特性就是
$$typeof 是 REACT_ELEMENT_TYPE 这个标识。他是用来标识 Element 是什么类型的。我们在写 JSX 代码的时候,所有的节点都是通过 createElement 进行创建的。那么他的 $$typeof 就永远都是 REACT_ELEMENT_TYPE。这个一定要记住,因为这个 $$typeof 在后续我们真正进行 React 应用更新的时候。他如何渲染到 React Dom 当中是经常要被用到的,是用来进行一个判断的。因为有一小部分跟平台有关,不是通过 createElement 创建,但是我们写应用的时候基本上都是 createElement 。 所以这里需要这个标识进行判断
type 是 我们传进来的type,也就是 createElement 接收的那个 type,这是没有变化的
key 就是 上面处理过的 key
ref 就是上面处理过的 ref
props 就是上面处理过的 props
这就是一个 ReactElement 这个方法如何操作以及最终返回的一个 类型。 看到这里就很模糊了。他就这么一点东西,他怎么就跟 dom 去关联起来呢。这个在后续 整体讲 React 更新的时候讲到。我们现在只需要记住这些内容。后续在讲流程的时候会涉及到。这个时候才会想到是这样用的