React源码解析13-ref

263 阅读3分钟

1.处理ref

1.创建该节点fiber对象的时候处理ref

2.stringRef才需要去挂载ref到refs上 函数ref只需要执行函数即可 对象ref直接改变current即可

流程:拿到ref 对比前后ref是否变化 没有变化则直接返回之前的ref函数 变化啦或者第一次则创建ref函数

这个函数传入value即实例(class就是this,dom就是dom元素)挂载到refs上

function coerceRef(
  returnFiber: Fiber,
  current: Fiber | null,
  element: ReactElement,
) {
  let mixedRef = element.ref;//!element就是该reactelement mixedRef就是传进来的ref
  if (
    mixedRef !== null &&
    typeof mixedRef !== 'function' &&//!函数ref
    typeof mixedRef !== 'object'//!obj的ref
  ) {
    //!string的ref的处理 因为string是没法挂载的    函数ref和obj的ref是不需要处理的 因为直接调用函数或者设置current即可
    if (__DEV__) {
      if (returnFiber.mode & StrictMode) {
        const componentName = getComponentName(returnFiber.type) || 'Component';
        if (!didWarnAboutStringRefInStrictMode[componentName]) {
          warningWithoutStack(
            false,
            '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 createRef() instead.' +
              '\n%s' +
              '\n\nLearn more about using refs safely here:' +
              '\nhttps://fb.me/react-strict-mode-string-ref',
            mixedRef,
            getStackByFiberInDevAndProd(returnFiber),
          );
          didWarnAboutStringRefInStrictMode[componentName] = true;
        }
      }
    }

    if (element._owner) {//!_owner就是当前的fiber
      const owner: ?Fiber = (element._owner: any);
      let inst;
      if (owner) {
        const ownerFiber = ((owner: any): Fiber);
        invariant(
          ownerFiber.tag === ClassComponent,
          'Function components cannot have refs.',
        );
        inst = ownerFiber.stateNode;//!拿到节点的实例 dom就是dom对象 类组件就是this
      }
      invariant(
        inst,
        'Missing owner for string ref %s. This error is likely caused by a ' +
          'bug in React. Please file an issue.',
        mixedRef,
      );
      const stringRef = '' + mixedRef;
      // Check if previous string ref matches new string ref
      if (
        current !== null &&
        current.ref !== null &&
        typeof current.ref === 'function' &&
        current.ref._stringRef === stringRef//!每次设置完ref时候 我们都会设置ref的._stringRef为我们的stringRef 用来更新组件的时候判断stringRef是否有变化 如果没变化 我们就不需要创建一个新的ref方法了 直接返回之前的ref方法
      ) {
        return current.ref;
      }
      //!生成新的ref方法
      const ref = function(value) {//!value就是传入该节点的实例 这个是当这个dom/class挂载的时候会调用这个ref方法传入自己的实例
        let refs = inst.refs;//!拿到rhis.refs这个对象
        if (refs === emptyRefsObject) {//!如果是空
          // This is a lazy pooled frozen object, so we need to initialize.
          refs = inst.refs = {};
        }
        if (value === null) {//!如果没有节点实例
          delete refs[stringRef];//!删除这个ref
        } else {
          refs[stringRef] = value;//!把这个ref设置到refs上
        }
      };
      ref._stringRef = stringRef;//!设置_stringRef方便比对前后的ref
      return ref;
    } else {
      invariant(
        typeof mixedRef === 'string',
        'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
      );
      invariant(
        element._owner,
        'Element ref was specified as a string (%s) 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://fb.me/react-refs-must-have-owner for more information.',
        mixedRef,
      );
    }
  }
  return mixedRef;
}

2.调用ref

在commitRoot时候真正把instance挂载上去

首先调用清空ref

然后再commitLifeCycles时候(因为此时dom已经挂载啦才有实例) 拿到ref函数 如果ref是函数则直接执行这个函数传入当前instance,因为stringRef的此时ref属性也在处理ref的时候转成啦函数,所以可以和函数ref一样直接调用函数即可,然后ref是对象的时候,直接调用ref.current=instance即可

function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;//!新的ref
  if (ref !== null) {
    const instance = finishedWork.stateNode;
    let instanceToUse;
    switch (finishedWork.tag) {
      case HostComponent:
        instanceToUse = getPublicInstance(instance);
        break;
      default:
        instanceToUse = instance;
    }
    if (typeof ref === 'function') {
      ref(instanceToUse);//!调用创建时候的ref函数 传入当前instance
    } else {
      if (__DEV__) {
        if (!ref.hasOwnProperty('current')) {
          warningWithoutStack(
            false,
            'Unexpected ref object provided for %s. ' +
              'Use either a ref-setter function or React.createRef().%s',
            getComponentName(finishedWork.type),
            getStackByFiberInDevAndProd(finishedWork),
          );
        }
      }

      ref.current = instanceToUse;//!对象ref直接设置current
    }
  }
}

总结:stringref需要在处理ref阶段创建一个ref函数转成函数ref形式 同时把ref挂载到refs对象上