【React Hooks原理 - useRef】

[复制链接]
发表于 2026-2-23 21:23:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
概述

在Function Component项目中当我们必要操纵dom的时间,第一时间想到的就是使用useRef这个Hook来绑定dom。但是这个仅仅是使用这个Hook而已,为了更好的学习React Hooks内部实现原理,知其以是然。以是本文根据源码从useRef的底子使用场景一步一步到内部实现来对其举行先容。
根本使用

在React中useRef是如许界说的:useRef生存一个可变的长期化引用,重新渲染时不会重值,更新值也不会渲染页面。
  1. export function useRef<T>(initialValue: T): { current: T } {
  2.   const dispatcher = resolveDispatcher();
  3.   return dispatcher.useRef(initialValue);
  4. }
复制代码
由代码能看出useRef吸收恣意范例的值,包罗平常值、函数、dom,然后颠末dispather举行派发处置惩罚,返回一个包罗current属性的对象引用,该对象平静凡Js对象划一,更新不收React束缚。
一样寻常在项目中useRef常用的有两个使用场景:


  • 通过useRef保持长期化的值,且不必要重新渲染
  • 通过useRef绑定dom,以便直接举行dom操纵
比如在项目中常用的定时器,我们都会在组件烧毁时通过clear函数举行定时器的扫除克制内存泄漏等标题,这时间就可以通过useRef来绑定timerId
  1. import { useRef } from 'react';
  2. let timerId = useRef(null);
  3. useEffect(() => {
  4.         timerId.current = setInterval(() => {
  5.                 console.log('setInterval');
  6.         }, 1000);
  7.         return () => {
  8.                 clearInterval(timerId.current);
  9.         }
  10. }, [])
  11. export default function Counter() {
  12.   return <></>
  13. }
复制代码
当我们必要举行dom操纵时,比如获取核心、主动滚动等,就可以通过useRef来绑定dom举行操纵
  1. import { useRef } from 'react';
  2. let inputRef = useRef(null);
  3. useEffect(() => {
  4.         // 在组件挂载后聚焦输入框
  5.     inputRef.current.focus();
  6. }, [])
  7. export default function Counter() {
  8.   return <input ref={inputRef} type='text' />
  9. }
复制代码
源码剖析

由于这里的mount、update逻辑很简单,并当useRef转达值/函数和转达dom时的处置惩罚是不一样的,以是我们以此来分开先容。
转达平常值时

当转达平常值时(包罗恣意范例值、函数),告急实验mountRef、updateRef两个函数。在mount挂载时创建一个包罗current属性的对象,然后在更新时返回雷同的引用memoizedState生存的,以是这里就在一起写了。
  1. function mountRef<T>(initialValue: T): { current: T } {
  2.   // 创建hook链表
  3.   const hook = mountWorkInProgressHook();
  4.   // ref初始化
  5.   const ref = { current: initialValue };
  6.   hook.memoizedState = ref;
  7.   // 返回ref
  8.   return ref;
  9. }
  10. function updateRef<T>(initialValue: T): { current: T } {
  11.     // 复用hook
  12.   const hook = updateWorkInProgressHook();
  13.   // 返回相同引用
  14.   return hook.memoizedState;
  15. }
复制代码
从源码能看出,useRef吸收一个初始化参数,可以为值/返回值的函数,然后在mountRef中创建了一个包罗current的对象,在updateRef中仍旧返回的该对象引用。
   如果初始值是函数,由于React内部不会做判断,直接将初始值赋予current,怎样是函数,则必要手动显式调用
  由于不管在mount挂载时,照旧在update更新时都是返回的对象引用,以此来保持长期化,当我们通过ref.current修改值时本质修改的是同一个引用对象,以是也不会触发重新渲染(object.is对比不停都是true)。
转达DOM时

当转达DOM时,在mount、update阶段也和传值一样,不会做任那边剖析返回相应的对象引用,但是如果转达的是DOM时,在Reconciler和谐器中通过React.createElement将JSX转换为React元素后举行fiber构造,在构造完成生产fiber树之后会进入到commit阶段,在该阶段会遍历节点对副作用和ref举行处置惩罚,此中在layout阶段会判断当前节点范例(tag)怎样是dom(tag === HostComponent)时,如果该dom有ref,则会对ref举行处置惩罚commitAttachRef函数
   在commit阶段,即renderer阶段,针对dom的差别状态和处置惩罚分为了三个阶段: Before Mutation、Mutation、Layout。有爱好的可以检察这篇文章【React源码 - Fiber架构之Renderer】
  以下commitAttachRef代码(省略了部分代码):
  1. function commitAttachRef(finishedWork: Fiber) {
  2.   // 获取节点的ref属性
  3.   const ref = finishedWork.ref;
  4.   if (ref !== null) {
  5.     // 获取dom实例,fiber.stateNode就是绑定的dom,在completeWork中会创建dom然后绑定到fiber.stateNode上
  6.     const instance = finishedWork.stateNode;
  7.     let instanceToUse;
  8.     switch (finishedWork.tag) {
  9.       case HostHoistable:
  10.       case HostSingleton:
  11.       case HostComponent:
  12.         // 获取dom实例
  13.         instanceToUse = getPublicInstance(instance);
  14.         break;
  15.       default:
  16.         instanceToUse = instance;
  17.     }
  18.     //
  19.     if (typeof ref === "function") {
  20.         // 将dom实例回传给传递的ref函数
  21.       finishedWork.refCleanup = ref(instanceToUse);
  22.     } else {
  23.         // 普通对象赋值到current
  24.       ref.current = instanceToUse;
  25.     }
  26.   }
  27. }
复制代码
从代码能看出该函数告急就是获取ref绑定的dom实例,然后根据传入ref的差别举行处置惩罚,如果是函数则将dom实例转达给函数由开辟者显式调用,否则则绑定到current属性上举行返回。
转达函数,显式处置惩罚ref的demo:
  1. import React, { useEffect, useRef } from 'react';
  2. function App() {
  3.   const divRef = useRef(null);
  4.   useEffect(() => {
  5.     if (divRef.current) {
  6.       console.log('Element mounted:', divRef.current);
  7.     }
  8.     return () => {
  9.       console.log('Element unmounted:', divRef.current);
  10.     };
  11.   }, []);
  12.   return <div ref={divRef}>Hello, World!</div>;
  13. }
  14. export default App;
复制代码
总结

基于以上相识,我们知道了useRef的底子使用和场景以及背后的代码处置惩罚,扼要总结一下就是:useRef用于长期化引用,返回平常Js引用,修改其值不会导致组件重新渲染。当转达平常值时,不会举行特别处置惩罚,只是返回雷同的对象引用。当绑定dom时,在mount、update阶段初始化对象,然后在commit阶段举行ref处置惩罚,函数显式处置惩罚则会将dom实例作为参数回传,平常值则会绑定到ref.current中。
题外话 - 号外

本文也是根据这些文章学习举行梳理在本身明确的底子上誊写的,如有标题,还请指正。

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表