Appearance
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 也有
render
和commit
阶段
HostComponent
在commit阶段
的mutation阶段
执行DOM
操作 所以,对应ref
的更新也是发生在mutation阶段
再进一步,mutation阶段
执行DOM
操作的依据为effectTag
所以,对于
HostComponent
、ClassComponent
如果包含ref
操作,那么也会赋值相应的flag
。(在render
阶段操作)
所以,ref
的工作流程可以分为两部分:
render阶段
为含有ref
属性的fiber
添加Ref flag
commit阶段
为包含Ref effectTag
的fiber
执行对应操作
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 flags
的fiber
(对应需要删除的DOM节点
),需要递归他的子树,对子孙fiber
的ref
执行类似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;
}
}
}