# useRef > 面试题:useRef 是干什么的?ref 的工作流程是怎样的?什么叫做 ref 的失控? ## useRef 各个阶段 ref 是英语 reference(引用)的缩写,在 React 中,开发者可以通过 ref 保存一个对 DOM 的引用。事实上,任何需要被引用的数据,都可以保存在 ref 上。在 React 中,出现过 3 种 ref 引用模式: - String 类型(已不推荐使用) - 函数类型 - { current : T } 目前关于创建 ref,类组件推荐使用 createRef 方法,函数组件推荐使用 useRef 用法如下: ```js const refContainer = useRef(initialValue); ``` **mount 阶段** mount 阶段调用的是 mountRef,对应的代码如下: ```js function mountRef(initialValue) { // 创建 hook 对象 const hook = mountWorkInProgressHook(); const ref = { current: initialValue }; // hook 对象的 memoizedState 值为 { current: initialValue } hook.memoizedState = ref; return ref; } ``` 在 mount 阶段,首先调用 mountWorkInProgressHook 方法得到一个 hook 对象,该 hook 对象的 memoizedState 上面会缓存一个键为 current 的对象 { current: initialValue },之后向外部返回该对象。 **update 阶段** update 阶段调用的是 updateRef,相关代码如下: ```js function updateRef(initialValue) { // 拿到当前的 hook 对象 const hook = updateWorkInProgressHook(); return hook.memoizedState; } ``` 除了 useRef 以外,createRef 也会创建相同数据结构的 ref: ```js function createRef(){ const refObject = { current: null } return refObject; } ``` ## ref 的工作流程 ref 创建之后,会挂在 HostComponent 或者 ClassComponent 上面,形成 ref props,例如: ```js // HostComponent
// ClassComponent
上图中,markRef 表示的就是标记 ref,相关代码如下:
```js
function markRef(current, workInProgress){
const ref = workInProgress.ref;
if((current === null && ref !== null) || (current !== null && current.ref !== ref)){
// 标记 Reg tag
workInProgress.flags != Ref;
}
}
```
有两种情况会标记 ref:
- mount 阶段并且 ref props 不为空
- update 阶段并且 ref props 发生了变化
标记完 ref 之后,来到了 commit 阶段,会在 mutation 子阶段执行 ref 的删除操作,删除旧的 ref:
```js
function commitMutationOnEffectOnFiber(finishedWork, root){
// ...
if(flags & Ref){
const current = finishedWork.alternate;
if(current !== null){
// 移除旧的 ref
commitDetachRef(current);
}
}
// ...
}
```
上面的代码中,commitDetachRef 方法要做的事情就是移除旧的 ref,相关代码如下:
```js
function commitDetachRef(current){
const currentRef = current.ref;
if(currentRef !== null){
if(typeof currentRef === 'function'){
// 函数类型 ref,执行并传入 null 作为参数
currentRef(null);
} else {
// { current: T } 类型的 ref,重置 current 指向
currentRef.current = null;
}
}
}
```
删除完成后,会在 Layout 子阶段重新赋值新的 ref,相关代码如下:
```js
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes){
// 省略代码
if(finishedWork.flags & Ref){
commitAttachRef(finishedWork);
}
}
```
对应的方法 commitAttachRef 就是用来重新赋值新 ref 的,相关代码如下:
```js
function commitAttachRef(finishedWork){
const ref = finishedWork.ref;
if(ref !== null){
const instance = finishedWork.stateNode;
let instanceToUse;
switch(finishedWork.tag){
case HostComponent:
// HostComponent 需要获取对应的 DOM 元素
instanceToUse = getPublicInstance(instance);
break;
default:
// ClassComponent 使用 FiberNode.stateNode 保存实例
instanceToUse = instance;
}
if(typeof ref === 'function'){
// 函数类型,执行函数并将实例传入
let retVal;
retVal = ref(instanceToUse);
} else {
// { current: T } 类型则更新 current 指向
ref.current = instanceToUse;
}
}
}
```
## ref 的失控
当我们使用 ref 保存对 DOM 的引用时,那么就有可能会造成 ref 的失控。
所谓 ref 的失控,开发者通过 ref 操作了 DOM,但是这一行为本来应该是由 React 接管的,两者产生了冲突,这种冲突我们就称之为 ref 的失控。
考虑下面这一段代码:
```js
function App(){
const inputRef = useRef(null);
useEffect(()=>{
// 操作1
inputRef.current.focus();
// 操作2
inputRef.current.getBoundingClientRect();
// 操作3
inputRef.current.style.width = '500px';
}, []);
return ;
}
```
在上面的三个操作中,第三个操作是不推荐的。
React 作为一个视图层框架,接管了大部分和视图相关的操作,这样开发者可以专注于业务上面的开发逻辑。
上面的三个操作中,前面两个并没有被 React 接管,所以当产生这样的操作时,可以百分百确定是来自于开发者的操作。但是在操作三中,并不能确定该操作究竟是 React 的行为还是开发者的行为,甚至两者会产生冲突。
例如我们再聚一个例子:
```js
function App(){
const [isShow, setShow] = useState(true);
const ref = useRef(null);
return (
Hello
}