Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《React源码》笔记
  • React-reconciler
  • hooks
东流
2026-03-30
目录

useRef

# useRef源码解析

useRef本质上和DOM无关,React中节点的ref属性可以函数,也可以是一个包含current的普通对象。通常情况下,将useRef的返回值作为节点ref的值,是因为useRef在组件更新前后,指向的是同一个内存地址,是不变的。因此React就设计成useRef和ref属性绑定。

# 源码分析

# useRef分析

useRef的源码实现非常简单,在函数组件的mount阶段和update阶段,分别调用mountRef和updateRef。其源码实现如下:

# mountRef方法

在首次渲染挂载时,useRef调用的是mountRef。

function mountRef(initialValue) {
  // 创建hook,将hook绑定fiber的updateQueue上并返回
  const hook = mountWorkInProgressHook();
  // 包装ref值
  const ref = { current: initialValue };
  // 将值保存到hook.memoizedState上
  hook.memoizedState = ref;
  return ref;
}
1
2
3
4
5
6
7
8
9

# updateRef

function updateRef(initialValue) {
  // 获取fiber上的新hook,新hook来自于旧fiber上的旧hook 
  const hook = updateWorkInProgressHook();
  // 返回新hook的memoizedState
  return hook.memoizedState;
}
1
2
3
4
5
6

通过mountRef和updateRef可知,userRef的hook在updateQueue上就是一个赋值/取值的操作仅此而已。

# ref属性

ref属性是一个特殊的属性,它的全流程如下:

flowchart TD
1[beginWork阶段] -->2{调用markRef方法\n是否在fiber.flags标记Ref?}
2--否-->3[后续无任何操作]
2 --是-->4[commit阶段的commitMutationEffectsOnFiber]
4-->5{fiber.flags&Ref?}
5 --是-->6[满足current!== null等条件才调用safelyDetachRef解绑]
6-->7[commitLayoutEffectOnFiber]
7-->8{fiber.flags&Ref?}
8-->9[commitAttachRef]
1
2
3
4
5
6
7
8
9

相关方法如下

# markRef方法

markRef方法就是判断需不需要在Fiber上打Ref标记。

function markRef(current, workInProgress) {
  // 取出新fiber上的ref
  const ref = workInProgress.ref;
  // 若新fiber上ref的值为null
  if (ref === null) {
    // 旧fiber存在,且旧ref不为null
    if (current !== null && current.ref !== null) {
     // 则说明ref被删除了,打上标记,后续需要清空旧ref
      workInProgress.flags |= Ref | RefStatic;
    }
  } else {
    // 判断ref的类型是否是函数或对象,若不是,则报错
    if (typeof ref !== 'function' && typeof ref !== 'object') {
      throw new Error(
        'Expected ref to be a function, an object returned by React.createRef(), or undefined/null.',
      );
    }
    // 组件首次渲染时,或者是更新阶段 新旧ref不同,则也需要打上标记
    if (current === null || current.ref !== ref) {
      workInProgress.flags |= Ref | RefStatic;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

首次渲染或者新旧fiber的ref不等,以及ref被删除,在fiber.flags上的标记都是一样。

# safelyDetachRef方法

在commitMutationEffectsOnFiber中调用safelyDetachRef方法,若当前不是首次渲染且fiber.flag有Ref标记则需要清理ref,此外在组件卸载时也需要调用fiber.flag,释放对DOM的引用。

function safelyDetachRef(
  current
) {
// current是指当前UI展示的fiber
  const ref = current.ref;
  const refCleanup = current.refCleanup;
// 若ref存在,且fiber.flags上标记了需要更新,则先进行ref的清理
  if (ref !== null) {
   // 先判断清理方法是否是函数
    if (typeof refCleanup === 'function') {
      try {
      // 执行清理函数
        refCleanup();
      } catch (error) {
        // 报错
      } finally {
        // 重置新fiber、旧fiber上的ref清理函数 
        current.refCleanup = null;
        const finishedWork = current.alternate;
        if (finishedWork != null) {
          finishedWork.refCleanup = null;
        }
      }
    } else if (typeof ref === 'function') {
    // 判断 ref是否是函数
      try {
      // 是函数,则再调用一遍
        ref(null);
      } catch (error) {
        // 报错
      }
    } else {
    // 若ref不是函数,则将ref.current重置
      ref.current = null;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 绑定赋值

一般在commitLayoutEffectOnFiber中调用,此时已经可以拿到DOM了。

function commitLayoutEffectLayoutOnFiber(
  finishedRoot,
  current,
  finishedWork,
  committedLanes,
) {
  const flags = finishedWork.flags;
  switch (finishedWork.tag) {
    case ClassComponent: {
      //...
      if (flags & Ref) {
        safelyAttachRef(finishedWork, finishedWork.return);
      }
    }
    case HostHoistable:
    case HostComponent: {
      if (flags & Ref) {
        safelyAttachRef(finishedWork, finishedWork.return);
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

如下的safelyAttachRef本质上就是调用commitAttachRef方法

function commitAttachRef(finishedWork) {
  const ref = finishedWork.ref;
  if (ref !== null) {
    let instanceToUse;
    switch (finishedWork.tag) {
      case HostHoistable:
      case HostSingleton:
      case HostComponent:
        instanceToUse = finishedWork.stateNode;
        break;
      case ViewTransitionComponent: {
       
        instanceToUse = finishedWork.stateNode;
        break;
      }
      default:
        instanceToUse = finishedWork.stateNode;
    }
    // 判断ref的类型
    if (typeof ref === 'function') {
      // 只有ref为函数且有返回值,返回值需要是函数,才可以在后续执行ref清理  
      finishedWork.refCleanup = ref(instanceToUse);
    } else {
      ref.current = instanceToUse;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 注意事项

在safelyDetachRef和commitAttachRef方法中操作的都是fiber.ref,在使用过程中经常与useRef一起使用,因此它与hook.memoizedState实际上是同一个引用地址,如下:

function FC()=>{
    const divRef = useRef();
    return <div ref={divRef}>Hello word</div>    
}
1
2
3
4

ref={divRef}就是将useRef返回值 和 fiber.ref关联起来。

编辑 (opens new window)
上次更新: 2026/04/03, 01:41:21
最近更新
01
useId
04-03
02
useImperativeHandle
04-01
03
memo组件
04-01
更多文章>
Theme by Vdoing | Copyright © 2024-2026 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式