1 )概述
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;
}
}
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;
}
关于 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();
}
}
定位到 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;
}
}
}
到 commitAllLifeCycles
时
function commitAllLifeCycles(
finishedRoot: FiberRoot,
committedExpirationTime: ExpirationTime,
) {
// ... 跳过很多代码
while (nextEffect !== null) {
// ... 跳过很多代码
// 注意这里
if (effectTag & Ref) {
recordEffect();
commitAttachRef(nextEffect);
}
// ... 跳过很多代码
}
}
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;
}
}
}
HostComponent
执行 getPublicInstance
export function getPublicInstance(instance: Instance): * {
return instance;
}