在开始看react源码前觉得代码量应该会非常庞大,然而在看完react源码后发现实际上react的源码只是一些API和数据结构的定义,真正的更新渲染逻辑是react-dom这块源码里控制的。
本此源码解析系列不包括dev环境下的代码,只对主流程代码进行讲解。
react的源码地址为:react
我们先来看下react的目录结构:
下面让我们看下react对我们暴露的API:/packages/react/src/React.js
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,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
React.createElement这个方法用于创建一个ReactElement,也就是jsx语法解析后的对象,如
// jsx
msg
// 上述jsx解析完成后的结构
React.createElement('div', { id: 'app' }, 'msg')
下面让我们看下createElement这个函数:
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
function hasValidRef(config) {
return config.ref !== undefined;
}
function hasValidKey(config) {
return config.key !== undefined;
}
// React.createElement('div', { id: 'btn' }, '提交');
// config包含组件上的所有props,包括:事件、key、ref、各种属性
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
// 这里遍历传入的config
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
// 不是key和ref
!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.
// 这里对children特殊处理,如果传入多个则props.children为数组,否则直接等于传入的children
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;
}
// Resolve default props
// 对默认值的特殊处理
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// props不包含key和ref
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
让我们再看几个例子来理解下
从上面几个例子可以看出type的几种取值(这里一定要理解type指向的值,后续更新流程中会有很多地方用到type):
现在让我们看下返回的element的结构
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,
};
return element;
};
上面还剩两个属性没讲到
1、:REACT_ELEMENT_TYPE,表示这是通过createElement创建的。在react中还有一种情况是通过ReactDOM.createPoratl()创建,这时它的'$$typeof'为REACT_PORTAL_TYPE。
2、'_owner':表示创建当前组件的class组件的实例,用于后续ref时使用。因为字符串类型的ref是要绑定到class实例的this.refs.xxx上的。
注意:这里留意一下$$typeof和type这两个属性,后续更新逻辑中会有大量对这两个属性的判断,比较容易混淆。
这个方法就是将createElement的type绑定一个固定的类型,后续不需要传type了。
export function createFactory(type) {
const factory = createElement.bind(null, type);
factory.type = type;
return factory;
}
用于复制一个element
export function cloneElement(element, config, children) {
// 校验下element不能为空,若为空则throw new Error
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;
// 复制config到element.props里,并更新key和ref
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;
// 如果传入child,则更新props.children
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);
}
根据$$typeof来判断是否是正确的element格式
export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}