react源码中小而美的函数之coerceRef

82 阅读3分钟
function coerceRef(returnFiber, current, element) {
  // 获取 ref
  var mixedRef = element.ref;

  // 如果 ref 是字符串且严格模式下或者有关于字符串 ref 的警告,则进行一些额外的检查
  if (
    mixedRef !== null &&
    typeof mixedRef !== 'function' &&
    typeof mixedRef !== 'object'
  ) {
    {
      // 在开发环境下,如果是字符串 ref,并且在严格模式下或者开启了字符串 ref 警告,则发出警告
      if (
        (returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) &&
        // 如果 owner 和 self 相等,说明这是一个函数组件
        !(element._owner && element._self && element._owner.stateNode !== element._self)
      ) {
        var componentName = getComponentNameFromFiber(returnFiber) || 'Component';

        // 发出警告
        if (!didWarnAboutStringRefs[componentName]) {
          {
            error(
              'A string ref, "%s", has been found within a strict mode tree. ' +
                'String refs are a source of potential bugs and should be avoided. ' +
                'We recommend using useRef() or createRef() instead. ' +
                'Learn more about using refs safely here: ' +
                'https://reactjs.org/link/strict-mode-string-ref',
              mixedRef
            );
          }

          didWarnAboutStringRefs[componentName] = true;
        }
      }
    }

    // 如果有 owner,则进行一些额外的检查
    if (element._owner) {
      var owner = element._owner;
      var inst;

      if (owner) {
        var ownerFiber = owner;

        // 如果 owner 不是 ClassComponent,则抛出错误,因为函数组件不能有字符串 ref
        if (ownerFiber.tag !== ClassComponent) {
          throw new Error(
            'Function components cannot have string refs. ' +
              'We recommend using useRef() instead. ' +
              'Learn more about using refs safely here: ' +
              'https://reactjs.org/link/strict-mode-string-ref'
          );
        }

        inst = ownerFiber.stateNode;
      }

      if (!inst) {
        throw new Error(
          "Missing owner for string ref " + mixedRef + ". This error is likely caused by a " +
            'bug in React. Please file an issue.'
        );
      }

      // 将 ref 转换成函数形式
      var resolvedInst = inst;

      {
        // 在开发环境下,检查字符串 ref 的强制转换
        checkPropStringCoercion(mixedRef, 'ref');
      }

      var stringRef = '' + mixedRef;

      // 如果当前的字符串 ref 和之前的字符串 ref 一样,直接返回之前的 ref
      if (
        current !== null &&
        current.ref !== null &&
        typeof current.ref === 'function' &&
        current.ref._stringRef === stringRef
      ) {
        return current.ref;
      }

      // 创建一个新的 ref
      var ref = function (value) {
        var refs = resolvedInst.refs;

        if (refs === emptyRefsObject) {
          refs = resolvedInst.refs = {};
        }

        if (value === null) {
          delete refs[stringRef];
        } else {
          refs[stringRef] = value;
        }
      };

      ref._stringRef = stringRef;
      return ref;
    } else {
      // 如果没有 owner,但 ref 不是字符串,则抛出错误
      if (typeof mixedRef !== 'string') {
        throw new Error('Expected ref to be a function, a string, an object returned by React.createRef(), or null.');
      }

      // 如果没有 owner,但 ref 是字符串,则抛出错误,说明可能是以下几种情况之一
      throw new Error(
        "Element ref was specified as a string (" + mixedRef + ") but no owner was set. This could happen for one of" +
          ' the following reasons:\n' +
          '1. You may be adding a ref to a function component\n' +
          "2. You may be adding a ref to a component that was not created inside a component's render method\n" +
          '3. You have multiple copies of React loaded\n' +
          'See https://reactjs.org/link/refs-must-have-owner for more information.'
      );
    }
  }

  // 如果不满足条件,直接返回 ref
  return mixedRef;
}

mixedRef 是字符串并且在严格模式或者开启了字符串 ref 警告时的处理:

const element = <div ref="myStringRef" />;
const returnFiber = /* some return fiber */;
const current = /* some current fiber */;

coerceRef(returnFiber, current, element);
// 输出警告,因为字符串 ref 在严格模式或者开启了字符串 ref 警告时会发出警告

element 有 owner 并且 owner 是 ClassComponent 时的处理

class MyComponent extends React.Component {
  render() {
    return <div ref="myStringRef" />;
  }
}

const myComponentInstance = new MyComponent();
const element = myComponentInstance.render();
const returnFiber = /* some return fiber */;
const current = /* some current fiber */;

coerceRef(returnFiber, current, element);
// 将字符串 ref 转换为函数 ref,并且在 owner 不是 ClassComponent 时抛出错误
  1. element 有 owner 但是 owner 不是 ClassComponent 时的处理:

    
    const element = <div ref="myStringRef" />;
    const returnFiber = /* some return fiber */;
    const current = /* some current fiber */;
    
    coerceRef(returnFiber, current, element);
    // 输出错误,因为函数组件不能有字符串 ref
    
  2. element 没有 owner 且 mixedRef 不是字符串时的处理:

    
    const element = <div ref={() => {}} />;
    const returnFiber = /* some return fiber */;
    const current = /* some current fiber */;
    
    coerceRef(returnFiber, current, element);
    // 不会输出警告或错误,直接返回 ref
    
  3. element 有 owner 但是 owner 不是 ClassComponent 时的处理:

const element = <div ref="myStringRef" />;
const returnFiber = /* some return fiber */;
const current = /* some current fiber */;

coerceRef(returnFiber, current, element);
// 输出错误,因为函数组件不能有字符串 ref

  1. element 没有 owner 且 mixedRef 不是字符串时的处理:
const element = <div ref={() => {}} />;
const returnFiber = /* some return fiber */;
const current = /* some current fiber */;

coerceRef(returnFiber, current, element);
// 不会输出警告或错误,直接返回 ref

  1. element 没有 owner 且 mixedRef 是字符串时的处理:
const element = <div ref="myStringRef" />;
const returnFiber = /* some return fiber */;
const current = /* some current fiber */;

coerceRef(returnFiber, current, element);
// 输出错误,因为没有 owner,但是字符串 ref 会要求有 owner