Skip to content
On this page

ref 的两种 数据结构function 或者 {current: any}

mount 和 update

js
function mountRef(initialValue) {
	const hook = mountWorkInProgressHook();
	const ref = {current: initialValue}; // 对象类型的数据结构
	hook.memoizedState = ref; // 保存到hook
	return ref
}

function updateRef(initialValue) {
	const hook = updateWorkInProgressHook();
	return hook.memoizedState; // 返回当前hook的数据
}

ref 的工作流程

ref 也有 rendercommit 阶段

HostComponentcommit阶段mutation阶段执行DOM操作 所以,对应ref的更新也是发生在mutation阶段 再进一步,mutation阶段执行DOM操作的依据为effectTag

所以,对于HostComponentClassComponent如果包含ref操作,那么也会赋值相应的flag。(在 render 阶段操作)

所以,ref的工作流程可以分为两部分:

  • render阶段为含有ref属性的fiber添加Ref flag
  • commit阶段为包含Ref effectTagfiber执行对应操作

render 阶段

js
// beginWork -> updateHostComponent -> markRef
function markRef(current, workInProgress) {
  var ref = workInProgress.ref;
  if (
		  current === null && ref !== null || // 首屏渲染
		  current !== null && current.ref !== ref // update
		) {
    workInProgress.flags |= Ref;
    {
      workInProgress.flags |= RefStatic;
    }
  }
}

// completeWork -> markRef
function markRef(workInProgress) {
	// 标记 flags 含有 ref
  workInProgress.flags |= Ref;
  {
    workInProgress.flags |= RefStatic;
  }
}

commit 阶段

commitRoot阶段mutation阶段中,对于ref属性改变的情况,需要先移除之前的ref

js
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  while (nextEffect !== null) {
    const flags = nextEffect.flags;
    // ...

    if (flags & Ref) {
	    // render 阶段 有打上 Ref 标识
      const current = nextEffect.alternate;
      if (current !== null) {
        // 移除之前的ref
        // 首屏渲染没有这一步
        commitDetachRef(current);
      }
    }
    // ...
  }
}

function commitDetachRef(current) {
	// 解绑 current节点的ref
  var currentRef = current.ref;
  if (currentRef !== null) {
    if (typeof currentRef === 'function') {
	    // 传入的ref是一个函数
      if ( current.mode & ProfileMode) {
        try {
          startLayoutEffectTimer();
          currentRef(null);
        } finally {
          recordLayoutEffectDuration(current);
        }
      } else {
        currentRef(null); // 传入 null 
      }
    } else {
	    // 传入对象就直接解绑
      currentRef.current = null;
    }
  }
} 

mutation阶段,对于Deletion flagsfiber(对应需要删除的DOM节点),需要递归他的子树,对子孙fiberref执行类似commitDetachRef的操作。

调用链(commit 阶段):commitDeletion——> unmountHostComponents——> -》-commitNestedUnmounts(递归子树执行) -> commitUnmount ——>ClassComponent | HostComponent中的 safelyDetachRef

js
function safelyDetachRef(current: Fiber) {
	// 删除旧的 ref
  const ref = current.ref;
  if (ref !== null) {
    if (typeof ref === 'function') {
      try {
        ref(null);
      } catch (refError) {
        captureCommitPhaseError(current, refError);
      }
    } else {
      ref.current = null;
    }
  }
}

在 commit 的 layout 阶段

js
// commitLayoutEffect -> commitAttachRef
// 必须在 render 阶段 标记了 Ref flag
function commitAttachRef(finishedWork: Fiber) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    // 获取ref属性对应的Component实例
    const instance = finishedWork.stateNode;
    let instanceToUse;
    switch (finishedWork.tag) {
      case HostComponent: // dom 节点
        instanceToUse = getPublicInstance(instance);
        break;
      default:
        instanceToUse = instance;
    }
    // 赋值ref
    if (typeof ref === 'function') {
      ref(instanceToUse);
    } else {
      ref.current = instanceToUse;
    }
  }
}