path:packages/react/src/ReactElementValidator.js
ReactElementValidator.js 模块中的方法对 ReactElement 中的
createElement、createFactory、cloneElement
方法进行了包装。
这些方法会在创建 React 元素时进行一些额外的检查,
并在检查不通过时打印一些警告信息。
这些方法被用在开发环境中。
这些检查包括:
- key 检查
- props 与元素类型是否匹配
对于这部分的学习,我们应该从另一方面认识一下这些警告信息。 这些警告是 React 传达给我们的很有用的信息,说明我们在使用 React 的过程中忽略了一些细节。 因此,我们需要尽可能修复这些警告。
createElementWithValidation
export function createElementWithValidation(type, props, children) {
const validType = isValidElementType(type);
// 我们在这种情况下发出警告,但不会抛出。
// 我们期望元素创建成功,在渲染中可能会出现错误。
if (!validType) {
// 在开发模式下打印警告
}
const element = createElement.apply(this, arguments);
// 如果使用模拟或自定义函数,结果可能为空。
// TODO: 当这些不再被允许作为 type 参数时,删除它。
if (element == null) {
return element;
}
// 如果 type 无效,那么跳过键警告
// 因为key 验证逻辑不期望非字符串/函数类型,并且可能抛出让人困惑的错误
// 我们不希望异常行为在 dev 和 prod 之间有所不同。
// (渲染将抛出一条有用的消息,一旦 type 被修复,就会出现键警告。)
if (validType) {
for (let i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i], type);
}
}
if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}
return element;
}
isValidElementType
用于判断目标是否是有效的 React 元素类型
createFactoryWithValidation
export function createFactoryWithValidation(type) {
const validatedFactory = createElementWithValidation.bind(null, type);
validatedFactory.type = type;
// Legacy hook: remove it
if (__DEV__) {
// do something
}
return validatedFactory;
}
cloneElementWithValidation
export function cloneElementWithValidation(element, props, children) {
const newElement = cloneElement.apply(this, arguments);
for (let i = 2; i < arguments.length; i++) {
validateChildKeys(arguments[i], newElement.type);
}
validatePropTypes(newElement);
return newElement;
}
其他方法
validateExplicitKey
/**
* Warn if the element doesn't have an explicit key assigned to it.
* This element is in an array. The array could grow and shrink or be
* reordered. All children that haven't already been validated are required to
* have a "key" property assigned to it. Error statuses are cached so a warning
* will only be shown once.
*
* @internal
* @param {ReactElement} element Element that requires a key.
* @param {*} parentType element's parent's type.
*/
function validateExplicitKey(element, parentType) {
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
const currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
// Usually the current owner is the offender, but if it accepts children as a
// property, it may be the creator of the child that's responsible for
// assigning it a key.
let childOwner = '';
if (
element &&
element._owner &&
element._owner !== ReactCurrentOwner.current
) {
// Give the component that originally created this child.
childOwner = ` It was passed a child from ${getComponentName(
element._owner.type,
)}.`;
}
setCurrentlyValidatingElement(element);
if (__DEV__) {
// do something
}
setCurrentlyValidatingElement(null);
}
validateChildKeys
/**
*
* 确保
* 1. 数组中的每个元素在静态位置定义了显式 key 属性
* 2. 或在可迭代对象中具有有效 key 属性
*
* @internal
* @param {ReactNode} node Statically passed child of any type.
* @param {*} parentType node's parent's type.
*/
function validateChildKeys(node, parentType) {
if (typeof node !== 'object') {
return;
}
// 遍历数组
if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) {
const child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
// This element was passed in a valid location.
if (node._store) {
node._store.validated = true;
}
} else if (node) {
// 遍历可迭代对象
const iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === 'function') {
// Entry iterators used to provide implicit keys,
// but now we print a separate warning for them later.
if (iteratorFn !== node.entries) {
const iterator = iteratorFn.call(node);
let step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
validateFragmentProps
/**
*
* 给定一个 fragment,验证它只能使用 fragment props
* @param {ReactElement} fragment
*/
function validateFragmentProps(fragment) {
setCurrentlyValidatingElement(fragment);
const keys = Object.keys(fragment.props);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key !== 'children' && key !== 'key') {
warning(
false,
'Invalid prop `%s` supplied to `React.Fragment`. ' +
'React.Fragment can only have `key` and `children` props.',
key,
);
break;
}
}
if (fragment.ref !== null) {
warning(false, 'Invalid attribute `ref` supplied to `React.Fragment`.');
}
setCurrentlyValidatingElement(null);
}
fragment 只能接收 children 属性和 key 属性,提供其他属性的时候会得到警告。
为 fragment 提供 ref 属性的时候会得到一个单独的警告。
validatePropTypes
/**
*
* 给定一个元素,验证它的 props 是否遵循 type 提供的 propTypes 定义。
*
* @param {ReactElement} element
*/
function validatePropTypes(element) {
const type = element.type;
if (type === null || type === undefined || typeof type === 'string') {
return;
}
const name = getComponentName(type);
let propTypes;
if (typeof type === 'function') {
propTypes = type.propTypes;
} else if (
typeof type === 'object' &&
(type.?typeof === REACT_FORWARD_REF_TYPE ||
// 注意:这里,Memo 只检查外部 props。
// 内部 props 在 reconciler 中检查
type.?typeof === REACT_MEMO_TYPE)
) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
setCurrentlyValidatingElement(element);
checkPropTypes(
propTypes,
element.props,
'prop',
name,
ReactDebugCurrentFrame.getStackAddendum,
);
setCurrentlyValidatingElement(null);
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) { // 写错属性名 propTypes -> PropTypes
propTypesMisspellWarningShown = true;
warningWithoutStack(
false,
'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
name || 'Unknown',
);
}
// 使用过时的 getDefaultProps 会得到警告,需要使用 defaultProps 替代
if (typeof type.getDefaultProps === 'function') {
warningWithoutStack(
type.getDefaultProps.isReactClassApproved,
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.',
);
}
}