React 深度学习:ReactElementValidator

1,087 阅读4分钟

path:packages/react/src/ReactElementValidator.js

ReactElementValidator.js 模块中的方法对 ReactElement 中的 createElementcreateFactorycloneElement 方法进行了包装。 这些方法会在创建 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.',
    );
  }
}