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;}复制代码
用于判断目标是否是有效的 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.', ); }}复制代码