枣庄网站开发公司,珠江夜游微信公众号,域名一定要备案吗,photoshop网课培训ref的实现过程 1 #xff09;概述
在更新流程当中如何去设置ref上面的对象的过程在我们创建fiber的时候去处理ref这个属性那我们什么时候创建fiber对象? 就是我们去更新某一个节点#xff0c;然后要去调和它的子节点的时候这个时候我们会对每一个子节点去创建这个fiber对象…ref的实现过程 1 概述
在更新流程当中如何去设置ref上面的对象的过程在我们创建fiber的时候去处理ref这个属性那我们什么时候创建fiber对象? 就是我们去更新某一个节点然后要去调和它的子节点的时候这个时候我们会对每一个子节点去创建这个fiber对象 创建这个fiber对象的过程我们就会去处理这个refcommit开始之前先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;}
}进入 coerceReffunction 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 就等于 ownerif (owner) {const ownerFiber ((owner: any): Fiber);invariant(ownerFiber.tag ClassComponent,Function components cannot have refs.,);// 然后它的 inst 就是 ownerFiber.stateNode// 也就是我们 class component那个 instance 也就是 thisinst 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 components 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;
}这就是在调和子节点的过程当中要处理 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 从之前挂载的地方去给它卸载下来看下这个 commitDetachRefif (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 doesnt 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: {// PlacementcommitPlacement(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;// Updateconst 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 执行 getPublicInstanceexport function getPublicInstance(instance: Instance): * {return instance;
}这个时候就完成了对于我们的 class component 上面的this去挂载ref它的一个过程这边最主要的是去注意对于 stringRef 在调和子节点的过程当中会对它进行一个预先的处理把它转化成一个方法以上就是ref在整个react应用更新的过程当中如何被实现的原理