文章目录
- useRef 源码解读
- mountRef
- updateRef
- ref 的生命周期(工作流程)
- 总结:
- render 阶段
- commit 阶段
- 总述
- 具体阐述
- safelyDetachRef
- commitAttachRef
useRef 源码解读
由于 string 类型的 ref 已不推荐使用,所以只针对 function |{current:any} 类型的 ref
与之前 useState 和 useEffect 一样,在 mount 阶段时,会调用 mountRef;在 update 阶段时,会调用 updateRef
mountRef
- 获取当前 hook 对象
- 将 hook.memoizedState 赋值为 ref
其实useRef 最终返回了就是一个普通对象,包含有 current 属性 , {current: initialValue}
updateRef
很简单
- 获取当前 hook 对象
- 返回 hook.memoizedState ,即 返回 ref 对象
若是类组件实现 ref ,使用的是 createRef 函数来创建的对象,而且也是一样的 有 current 属性的对象
ref 的生命周期(工作流程)
一般我们在 HostComponent,ClassComponent,FunctionComponent ,ForwardRef 里面都可以使用 ref 属性,ref 属性的值一般是 一个函数或者是 一个包含current 属性的对象
比如:
<Home ref={homeRef} />
<div ref={divRef} />
对于一个 函数类型的 ref 来说,在 ref的不同生命周期中,这个 function ref 都会被调用,并且它的传参是不一样的;
对于一个包含 current 属性的对象来说,在 ref的不同生命周期中,current 属性对应的值也是不一样的
总结:
ref 的工作流程可以分为两部分:
- render 阶段为含有 ref 属性的 fiber 添加 Ref effectTag
- commit 阶段为包含 Ref effectTag 的 fiber 执行对应的操作
render 阶段
主要是 markRef 函数
在首屏渲染时,并且有ref 属性的情况下,
或者 在 update 时,并且 ref 改变的情况下,都会为当前 fiber 加上 Ref 的 effectTag
commit 阶段
总述
当标记完之后,进入 commit 阶段。
-
对于 ref 属性改变的情况,需要先移除之前的 ref
-
对于 Deletion effectTag 的 fiber(对应需要删除的DOM 节点),需要递归它的子树,对 子孙 fiber 的 ref 执行类似 commitDetachRef 的操作,这一步主要是 safelyDetachRef 函数
-
进入 ref 的赋值阶段,commitLayoutEffect 会执行 commitAttachRef (赋值 ref)
具体阐述
当标记完之后,进入 commit 阶段。
-
对于 ref 属性改变的情况,需要先移除之前的 ref
-
对于 Deletion effectTag 的 fiber(对应需要删除的DOM 节点),需要递归它的子树,对 子孙 fiber 的 ref 执行类似 commitDetachRef 的操作,这一步主要是 safelyDetachRef 函数
safelyDetachRef
递归子树,对子树中每个 fiber 节点执行 commitunmount
在 commitunmount 中,会根据fiber 的类型来做不同的处理
比如说若是 classComponent 类型的话,不仅会执行 safelyDetachRef ,还会判断 实例中是否存在 componentWillUnmount 的生命周期函数,若存在,还会执行 这个生命周期函数:
在 safelyDetachRef 函数中,若是 函数类型的 ref,会执行 ref 函数,参数为null;
若是对象类型的,就是把 current 属性赋值为 null:
- 进入 ref 的赋值阶段,commitLayoutEffect 会执行 commitAttachRef (赋值 ref)
commitAttachRef
- 通过当前fiber的 tag 来获取对应的实例,对于 HostComponent ,实例就是获取到的 DOM 节点,其他情况就是 fiber.stateNode
- 判断 ref 的类型,如果是 函数类型,调用 ref 函数并将实例传过去;若不是,则将 ref.current 赋值为该 实例