【React】源码遨游(一) createElement()
作者:互联网
React源码遨游
今天新开的专栏主要围绕着React源码进行学习,切入口主要围绕着React的顶层API(即React)。
createElement()
createElment()
隶属于顶层ApiReact
的 /src/ReactElement.js
中。
首先我们看一下创建组件的代码,然后逐句分析一下。
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 (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
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,
);
}
方法顶部首先定义了一些变量,先不管他们。
config
是参数传进来的配置信息。首先看如果这个变量不为空,我们会从配置信息中提取哪些要素:
// 如果配置不为空
if (config != null) {
// 如果配置中的 ref 有效,则将前面定义的 ref 赋值为配置中的 ref 属性
if (hasValidRef(config)) {
ref = config.ref;
// 开发环境中
if (__DEV__) {
// 如果配置中的 ref 属性是一个字符串,则报一个警告(未来不支持字符串传入 ref )
warnIfStringRefCannotBeAutoConverted(config);
}
}
// 如果配置中的 key 有效,则将前面定义的 key 赋值为配置中的 key属性
if (hasValidKey(config)) {
key = '' + config.key;
}
// 也是之前定义的 self 和 source, 如果配置中的私有变量 __self, __source不为空,则赋值。
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
// 除了上述提到的属性之外,遍历其余属性,赋值到之前定义的 prop 属性中
for (propName in config) {
if ( // 属性名要不是保留字
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
// 满足条件的属性,赋值到 prop 变量中
props[propName] = config[propName];
}
}
}
接下来处理children
参数
// 首先确定子元素的数量,因为 children 不一定只有一个
const childrenLength = arguments.length - 2;
if (childrenLength === 1)
// 如果只有一个,那么就把这个 children 赋值给我们定义的 props 变量的 children 属性即可
props.children = children;
} else if (childrenLength > 1) {
// 如果大于一个,定义一个children的数组,将所有children都放进去
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
// 如果再开发环境,则冻结这个childArray数组(性能提升)
if (Object.freeze) {
Object.freeze(childArray);
}
}
// 将子元素数组赋值到 props 变量的 children 属性
props.children = childArray;
}
接下来我们来处理第一个参数type
// Resolve default props
// 如果 type 参数中的 defaultProps属性不为空
if (type && type.defaultProps) {
// 新建一个 defaultProps 变量,赋值为参数 type 的 defaultProps
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
// 如果之前我们定义的 props 中有属性为 undefined,则我们从defaultProps中提取默认值
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
// 如果 key 或者 ref 不为空
if (key || ref) {
// 如果传入的 type 是一个函数,则显示名设置为函数的显示名、名、未知。
// 否则就显示 type 名
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) { // 如果 key 属性不为空
// 如果通过 props 对象获取 key 时报错
defineKeyPropWarningGetter(props, displayName);
}
if (ref) { // 如果 ref 不为空
// 如果通过 props 对象获取 ref 时报错
defineRefPropWarningGetter(props, displayName);
}
}
}
// 返回 ReactElement
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
看一下最后返回的ReactElement
:
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// 这个type能让我们判断这个元素是一个react元素
$$typeof: REACT_ELEMENT_TYPE,
// 元素拥有的内构属性的值
type: type,
key: key,
ref: ref,
props: props,
// 记录创建元素的拥有者
_owner: owner,
};
if (__DEV__) { // 开发模式
// validated是可更改的,所以用一个内部私有变量_store储存,从而可以冻结整个对象。
// 未来可以用一个WeakMap对象来代替
element._store = {};
// 在开发模式下,把validated设置为不可遍历,使测试更简单
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// _self和_source是内部私有变量
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
// 在测试环境中不同地方创建的元素应被认为平等,所以将_source定义为不可遍历
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.js
里面的其他方法,说明都在注释中:
function hasValidRef(config) {
if (__DEV__) {
// 如果是开发模式,查看 config 中定义的 get 方法, 如果 get 中的 isReactWarning
// 为真(说明该 ref 有警告,在开发模式不有效,返回false
if (hasOwnProperty.call(config, 'ref')) {
const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
// 如果不是开发模式,则直接判断 ref 是否为空
return config.ref !== undefined;
}
// key 的验证方式和上述是一样的
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;
}
// 定义属性的get方法
function defineKeyPropWarningGetter(props, displayName) {
// 这个变量的意思是访问Key的时候是否有警告
const warnAboutAccessingKey = function() {
if (__DEV__) {
// 如果特殊属性key的警告没有显示过
if (!specialPropKeyWarningShown) {
// 将这个flag先置为true
specialPropKeyWarningShown = true;
// 在开发者工具中输出error
console.error(
'%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://reactjs.org/link/special-props)',
displayName,
);
}
}
};
// 将这个属性的 isReactWarning 置为 true
warnAboutAccessingKey.isReactWarning = true;
// 用对象的 defineProperty 方法,定义key的属性,get方法设置为 warnAboutAccessingKey
Object.defineProperty(props, 'key', {
get: warnAboutAccessingKey,
configurable: true,
});
}
// 定义 ref 的get方法也是一样的。
function defineRefPropWarningGetter(props, displayName) {
const warnAboutAccessingRef = function() {
if (__DEV__) {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
console.error(
'%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://reactjs.org/link/special-props)',
displayName,
);
}
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true,
});
}
// 创建一个方法,生产提供类型的ReactElement
export function createFactory(type) {
const factory = createElement.bind(null, type);
// 暴露Factory的type属性,这样就能轻松访问type属性。
// 这不是一个构造方法
factory.type = type;
return factory;
}
// 克隆元素,并且替换一个新的key
export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);
return newElement;
}
// 克隆元素方法
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;
// 传入原始元素
const props = Object.assign({}, element.props);
// 下面和createElement的处理一致
let key = element.key;
let ref = element.ref;
// self 属性设置为元素的_self,因为owner保持为原来的owner不变
const self = element._self;
// source设置为克隆元素的_souce,以更好的溯源
const source = element._source;
// Owner也保留之前的owner,而不是被克隆的元素
let owner = element._owner;
if (config != null) {
if (hasValidRef(config)) {
// 从配置中获取 ref, 并且将owner设置为 ReactCurrentOwner.current
ref = config.ref;
owner = ReactCurrentOwner.current;
}
// 下述操作和createElement一致
if (hasValidKey(config)) {
key = '' + config.key;
}
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) {
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
}
// 下述操作和createElement一致
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);
}
标签:type,propName,ref,React,源码,key,props,createElement,config 来源: https://blog.csdn.net/qq_35714301/article/details/118885074