当前位置:   article > 正文

React16源码: React中处理ref的核心流程源码实现

React16源码: React中处理ref的核心流程源码实现

ref的实现过程


1 )概述

  • 在更新流程当中如何去设置ref上面的对象的过程
  • 在我们创建fiber的时候去处理ref这个属性
  • 那我们什么时候创建fiber对象?
    • 就是我们去更新某一个节点,然后要去调和它的子节点的时候
    • 这个时候我们会对每一个子节点去创建这个fiber对象
  • 创建这个fiber对象的过程,我们就会去处理这个ref
  • commit开始之前先detach

2 )源码

定位到 packages/react-reconciler/src/ReactChildFiber.js#L1108

function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  expirationTime: ExpirationTime,
): Fiber {
  // ... 跳过很多代码
  while (child !== null) {
    // TODO: If key === null and child.key === null, then this only applies to
    // the first item in the list.
    if (child.key === key) {
      if (
        child.tag === Fragment
          ? element.type === REACT_FRAGMENT_TYPE
          : child.elementType === element.type
      ) {
        // ... 跳过很多代码

        // 注意这里
        existing.ref = coerceRef(returnFiber, child, element);
        
        // ... 跳过很多代码
      } else {
        deleteRemainingChildren(returnFiber, child);
        break;
      }
    } else {
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }
  
  if (element.type === REACT_FRAGMENT_TYPE) {
    // ... 跳过很多代码
    return created;
  } else {
    // ... 跳过很多代码
    // 注意这里
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 进入 coerceRef
    function coerceRef(
      returnFiber: Fiber,
      current: Fiber | null,
      element: ReactElement,
    ) {
      let mixedRef = element.ref; // 拿到 ref
      // function ref 和 object ref 是不需要经过特殊处理的
      // 自己处理节点对象挂载到 class component 的 this 上面这一个过程
      // 对于 string ref,它只是一个string,它没有任何功能,它的挂载是要react这边来帮着去做的
      // 所以这边主要去 去处理一下 string ref 的一个实现过程
      if (
        mixedRef !== null &&
        typeof mixedRef !== 'function' &&
        typeof mixedRef !== 'object'
      ) {
        if (__DEV__) {
          // 跳过
        }
        // 在 ReactElement.js 中可看到  _owner 是 ReactCurrentOwner.current
        // 在更新 class component 的时候,调用 finishClassComponent 就会设置
        // ReactCurrentOwner.current = workInProgress
        // 在后面调用 instance.render() 去重新渲染子节点的过程中
        // 就会调用 React.createElement, 因为 ref 只有
        // 因为ref它肯定是在 class component,它这个过程当中才会被创建的
        // 因为只有 class component 有 this 去挂载 ref 的那个对象
        // 所以我们在调用 instance.render() 的时候,那么在 react element 里面拿到的这个 ReactCurrentOwner.current
        // 就是我们那个 class component,它对应的fiber对象
        if (element._owner) {
          const owner: ?Fiber = (element._owner: any);
          let inst;
          // 有了这个 fiber 对象之后,那么我们可以拿到它的 _owner,拿到它的 _owner 之后
          // 如果 owner 的存在,ownerFiber 就等于 owner
          if (owner) {
            const ownerFiber = ((owner: any): Fiber);
            invariant(
              ownerFiber.tag === ClassComponent,
              'Function components cannot have refs.',
            );
            // 然后它的 inst 就是 ownerFiber.stateNode
            // 也就是我们 class component,那个 instance 也就是 this
            inst = ownerFiber.stateNode;
          }
          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
          // 有了 inst 之后,可以为它去构建一个方法
          // 这边是一个对比,就是说我们每次设置完这个 ref 之后,都会给它设置一个属性 _stringRef
          // 用来在我们更新这个组件的过程当中,去判断一下它这个 _stringRef 对应的那个值是否有变化
          if (
            current !== null &&
            current.ref !== null &&
            typeof current.ref === 'function' &&
            current.ref._stringRef === stringRef
          ) {
            // 如果没有变化,我们不需要去为它重新生成一个方法了,我们只需要 return 就可以了
            return current.ref;
          }
          // 对于新的情况,我们就需要去生成一个方法
          // 这个 value 就是后期dom节点或者是class instance它被挂载的时候
          // 它会调用这个 ref 这个方法,然后传入它自己的那个实例
          // 也就是给一个 dom 节点设置了 ref 之后,在后期就是 commitRoot 的过程当中,这个节点最终被挂载到 dom 上面了
          // 这个时候会把这个 dom 节点去调用 ref 这个方法,然后作为参数传入进来
          // 这个时候去设置到就是当前去创建这个 ref 的时候,这个 class component的对象上面的 ref 这个属性上面
          // 也就达到了 ref 可以设置在 this.refs 上面这个功能
          const ref = function(value) {
            let refs = inst.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];
            } else {
              refs[stringRef] = value;
            }
          };
          ref._stringRef = stringRef;
          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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 这就是在调和子节点的过程当中,要处理 ref 的一个内容
    • 因为 stringRef 它是一个特殊的存在,它没有什么功能性
    • 而对于 function ref 传进来的就是一个方法,可以直接调用它
    • 而对于 object ref,只需要设置它的 .current 就可以了
    • 这也是为什么以后 string ref 要被移除的一个原因
    • 因为它比较麻烦,需要我们自己去处理
    • 这是我们去处理 ref 这个属性的过程

关于 commit开始之前先detach
定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L392

查看 commitAllHostEffects

function commitAllHostEffects() {
  while (nextEffect !== null) {
    if (__DEV__) {
      ReactCurrentFiber.setCurrentFiber(nextEffect);
    }
    recordEffect();

    const effectTag = nextEffect.effectTag;

    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    // 对于有 ref 这个 SideEffect 的节点
    // 如果current不等于null,要先调用 commitDetachRef
    // 先把这个 ref 从之前挂载的地方去给它卸载下来,看下这个  commitDetachRef
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }

    // The following switch statement is only concerned about placement,
    // updates, and deletions. To avoid needing to add a case for every
    // possible bitmap value, we remove the secondary effects from the
    // effect tag and switch on that value.
    let primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement: {
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        // TODO: findDOMNode doesn't rely on this any more but isMounted
        // does and isMounted is deprecated anyway so we should be able
        // to kill this.
        nextEffect.effectTag &= ~Placement;
        break;
      }
      case PlacementAndUpdate: {
        // Placement
        commitPlacement(nextEffect);
        // Clear the "placement" from effect tag so that we know that this is inserted, before
        // any life-cycles like componentDidMount gets called.
        nextEffect.effectTag &= ~Placement;

        // Update
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion: {
        commitDeletion(nextEffect);
        break;
      }
    }
    nextEffect = nextEffect.nextEffect;
  }

  if (__DEV__) {
    ReactCurrentFiber.resetCurrentFiber();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L623

查看 commitDetachRef

function commitDetachRef(current: Fiber) {
  const currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') {
      currentRef(null);
    } else {
      currentRef.current = null;
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

commitAllLifeCycles

function commitAllLifeCycles(
  finishedRoot: FiberRoot,
  committedExpirationTime: ExpirationTime,
) {
  // ... 跳过很多代码
  while (nextEffect !== null) {
    // ... 跳过很多代码
    // 注意这里
    if (effectTag & Ref) {
      recordEffect();
      commitAttachRef(nextEffect);
    }
    // ... 跳过很多代码
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 再次调用 commitAttachRef 把真正的更新过后的节点给它挂载上去
    function commitAttachRef(finishedWork: Fiber) {
      const ref = finishedWork.ref;
      if (ref !== null) {
        const instance = finishedWork.stateNode;
        let instanceToUse;
        switch (finishedWork.tag) {
          case HostComponent:
            instanceToUse = getPublicInstance(instance); // 获取到了 dom节点对应到的实例
            break;
          default:
            instanceToUse = instance;
        }
        // function 的处理
        if (typeof ref === 'function') {
          ref(instanceToUse);
        } 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;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 如果是 HostComponent 执行 getPublicInstance
      export function getPublicInstance(instance: Instance): * {
        return instance;
      }
      
      • 1
      • 2
      • 3
  • 这个时候就完成了对于我们的 class component 上面的this,去挂载ref它的一个过程
  • 这边最主要的是去注意对于 stringRef 在调和子节点的过程当中
  • 会对它进行一个预先的处理,把它转化成一个方法
  • 以上就是ref在整个react应用更新的过程当中,如何被实现的原理
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/65829
推荐阅读
相关标签
  

闽ICP备14008679号