React Internals Part One Overview
October 3, 2020 • ☕️☕️ 8 min read
to be organized…
Here are some notes I took while learning React source code.
Note1: I will focus on the contents of the packages folder, rather than the overall architecture.
Note2: My analysis process will rely heavily on the sourcegraph plugin, which you can download from here. Also I’ll post sourcegraph links of the code as I analyze it in case you want to follow with me.
Note3: This article will be updated as the official React repository is updated, at this time of writing React version is 16.13.1.
ReactElement
I guess this API is the most familiar and yet the strangest one.
Most of us don’t need to call this API directly when we’re developing, but it plays an essential role in helping us convert JSX into react components.
To unravel its mysteries, let’s get hands dirty from packages/react/src/React.js:
// Remove all the import declarations for simplicity
const createElement = __DEV__
? createElementWithValidation
: createElementProd;
const cloneElement = __DEV__
? cloneElementWithValidation
: cloneElementProd;
const createFactory = __DEV__
? createFactoryWithValidation
: createFactoryProd;
const Children = {
map,
forEach,
count,
toArray,
only,
};
export {
Children,
createMutableSource,
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useMutableSource,
useReducer,
useRef,
useState,
REACT_FRAGMENT_TYPE as Fragment,
REACT_PROFILER_TYPE as Profiler,
REACT_STRICT_MODE_TYPE as StrictMode,
REACT_DEBUG_TRACING_MODE_TYPE as unstable_DebugTracingMode,
REACT_SUSPENSE_TYPE as Suspense,
createElement,
cloneElement,
isValidElement,
ReactVersion as version,
ReactSharedInternals
as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
// Deprecated behind disableCreateFactory
createFactory,
// Concurrent Mode
useTransition,
startTransition,
useDeferredValue,
REACT_SUSPENSE_LIST_TYPE as SuspenseList,
REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden,
// enableBlocksAPI
block,
// enableFundamentalAPI
createFundamental as unstable_createFundamental,
// enableScopeAPI
REACT_SCOPE_TYPE as unstable_Scope,
useOpaqueIdentifier as unstable_useOpaqueIdentifier,
};
We can see that this file exists as an API export. Here I’ll pick some common APIs and briefly review them below:
-
Children
This object contains several APIs, such as
React.Children.map
,React.Children.only
, etc. to help you handle React children prop. These methods circumvent some drawbacks of using JavaScript’s native array APIs directly, such as the fact that rendering children withArray.prototype.map
will report an error when the children type is not an array, whileReact.children.map
will just ignore it!You can explore more through links below:
-
createRef
creates a ref that can be attached to React elements via the ref attribute.
You can explore more through links below(and findout why stringRef is legacy):
// in class component
class App extends React.component {
constructor() {
this.demoRef = React.createRef()
}
render() {
return <div ref={this.demoRef} />
// or
return <div ref={ref => this.demoRef = ref}>
}
}
-
Component
&PureComponent
From packages/react/src/ReactBaseClasses.js, we know that there is no essential difference between the two classes, except that the
PureComponent
has an additional property(which is mounted on its prototype):
pureComponentPrototype.isPureReactComponent = true;
And this property is used to determine if the component’s props and state should be shallowly compared or not, and thus whether the component needs to be updated:
// packages/react-reconciler/src/ReactFiberClassComponent.new.js
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps)
|| !shallowEqual(oldState, newState)
);
}
check here for the source code.
-
createContext
You can explore more through links below:
-
fowardRef
Sometimes we may need to pass a ref through a component to its children. This is exacly where
fowardRef
shines. -
useXXX
hooksHooks are a really powerful addition in React 16.8. It makes resusing logic between defferent component a snap.
-
createElement
createElement
is used to buildReactElement
, usually we use Babel to compile JSX as a result of a call from theReact.createElement
function:// JSX <p className="text">hello world</p> // compile result React.createElement('p', {className: 'text'}, 'hello world')
So now we have a basic understanding of React APIs, let’s get back to the original topic: React.createElement
. If you have a sharp eye, you probably noticed that the createElement
exported by packages/react/src/React.js can be different depending on the environment variables:
const createElement = __DEV__
? createElementWithValidation
: createElementProd
As the name implies, if your code is in a development environment, the createElement
will do some additional checks on the given parameters, such as type
etc. To make this part easy to read, let’s just interpret production version createElement
, which is createElementProd
in the above code.
According to the entry file, we can find the code for the createElement
located at packages/react/src/ReactElement.js, which can be roughly divided into three sections:
- Gather component props from the second argument
config
as a new object namedprops
, excluding the four special properties(RESERVED_PROPS
)key
,ref
,self
, andsource
(these are handled separately and will be passed directly to the next functionReactElement
).
//...
let propName;
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
//...
- Handle the
children
prop(The third and subsequent parameters are all children prop)
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;
}
- Handle
defaultProps
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
In the end, createElement
just pass all of these into ReactElement
funtion.
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
So what did ReactElement
do with these parameters?
The answer is really simple. ReactElement
simply returns an object containing all the parameters except self
and source
, with an additional property $$typeof
with a value of REACT_ELEMENT_TYPE
:
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,
};
// omit the dev code
return element;
}
So this is it. In short, createElement
creates an element object for us, which contains some key attributes such as $$typeof
, type
, props
, key
, ref
and so on. As for what this object is used for and how it helps to update the React Dom, I’ll explain that later.
Further reading:
Why Do React Elements Have a $$typeof Property? by Dan Abramov