React开发中createRef和自定义Ref对象该怎么选

来源:个人站长网作者:美谷头衔:网络博主
导读:本期聚焦于美谷创作的《React开发中createRef和自定义Ref对象该怎么选》,敬请观看详情。在React组件开发中,操作DOM或者获取组件实例是常见需求,ref是React提供的核心能力之一。很多开发者在使用ref时会遇到选择困难,不知道该使用官方提供的createRef方法,还是自己封装自定义Ref对象。这两种方式看似都能实现引用管理,但在适用场景、底层逻辑、使用限制上都有明显区别。本文会详细拆解createRef的实现原理,分析自定义Ref对象的适用场景,对比两者的核心差异,结合具体的代码示例说明不同场景下的最优选择,帮助开发者在实际开发中做出更合理的决策,避免因为选择不当导致的代码冗余或者功能异常。

在 React 组件开发中,ref 是连接组件与真实 DOM 节点或类组件实例的重要桥梁。官方提供的 createRef() 方法适合大多数基础引用场景,而自定义 Ref 对象则允许开发者在 ref 赋值过程中加入额外逻辑。两者都能完成引用管理,但设计目标、扩展能力和维护成本并不相同。选择时不能只看能否拿到节点,还要看是否需要监听赋值、保持对象稳定、兼容函数组件,以及是否会让组件行为变得复杂。

一、createRef 的基本用法与适用边界

createRef 是 React 官方提供的创建 ref 对象的方法。调用后会返回一个普通对象,其 current 属性初始为 null。当 ref 被挂载到 DOM 元素或类组件实例上后,React 会把对应节点或实例写入 current。开发者随后可以通过 current 调用 DOM API 或访问实例方法。由于它语义明确、使用简单,是类组件中获取输入框、画布、弹窗等节点时的常见选择。

import React, { createRef, Component } from 'react';

class DemoComponent extends Component {
  constructor(props) {
    super(props);
    // 创建 ref 对象,current 初始为 null
    this.inputRef = createRef();
  }

  handleFocus = () => {
    // 组件挂载后 current 指向输入框 DOM 节点
    this.inputRef.current.focus();
  };

  render() {
    return (
      <div>
        <input ref={this.inputRef} type="text" />
        <button onClick={this.handleFocus}>聚焦输入框</button>
      </div>
    );
  }
}

export default DemoComponent;

从实现角度看,createRef 的核心非常轻,它只是返回一个带 current 属性的对象。理解这一点有助于判断它的边界:它本身不负责生命周期管理,也不会在赋值时执行额外逻辑,更不会帮你判断节点是否可用。真正完成赋值的是 React 的 ref 挂载机制。

function createRef() {
  const refObject = {
    current: null
  };
  return refObject;
}

使用 createRef 时,一个关键细节是对象稳定性。每次调用 createRef() 都会产生一个新对象,因此类组件通常把调用放在构造函数中,而不是 render 方法中。如果在 render 中创建,组件重新渲染时 ref 对象会变化,可能导致不必要的重新挂载或引用失效。函数组件中虽然也可以调用 createRef(),但更推荐 useRef(),因为它会在组件整个生命周期内保持同一个对象。

二、自定义 Ref 对象的实现方式与能力扩展

自定义 Ref 对象并不是另一个官方 API,而是开发者按照 ref 协议自行构造的对象。React 在挂载 ref 时,核心检查对象是否具备 current 属性,并把节点或实例写入该属性。只要对象满足这一基本约定,就可以承载额外的属性、方法或赋值监听逻辑。相比 createRef 的纯容器形态,自定义 Ref 更接近一个小型引用管理器。

下面的示例通过 gettersetter 扩展了 current 的赋值过程。React 仍然只负责执行赋值,但对象内部可以记录日志、缓存引用或触发后续处理。这种方式适合需要观察 ref 变化、统一处理引用赋值、或把引用状态纳入组件内部管理的场景。

import React, { Component } from 'react';

class CustomRefDemo extends Component {
  constructor(props) {
    super(props);

    // 使用闭包保存真实值,避免直接暴露可变属性
    let currentValue = null;

    // React 只要求 ref 对象具备 current 属性,
    // 这里通过 getter 和 setter 扩展赋值时的监听能力
    this.customRef = {
      get current() {
        return currentValue;
      },
      set current(value) {
        console.log('ref 被赋值:', value);
        currentValue = value;
      },
      getAssignedValue() {
        return currentValue;
      }
    };
  }

  componentDidMount() {
    // 挂载完成后可以读取 ref 的当前值
    console.log('当前输入框节点:', this.customRef.current);
    console.log('扩展方法读取的值:', this.customRef.getAssignedValue());
  }

  render() {
    return (
      <div>
        <input ref={this.customRef} type="text" />
      </div>
    );
  }
}

export default CustomRefDemo;

回调函数形式的 ref 也可以看作一种自定义引用管理。React 会在组件挂载、更新或卸载时调用回调,并传入节点或 null。与对象 ref 相比,回调 ref 更灵活,适合在挂载瞬间执行一次性操作,例如初始化第三方库、获取尺寸、注册事件。缺点是回调函数如果每次渲染都重新创建,可能导致重复调用,因此类组件中常把回调定义为稳定方法,函数组件中可结合 useCallback 保持引用稳定。

import React, { Component } from 'react';

class CallbackRefDemo extends Component {
  handleInputRef = (node) => {
    // 挂载时 node 为 DOM 节点,卸载时 node 为 null
    if (node) {
      console.log('输入框已挂载');
      node.focus();
    } else {
      console.log('输入框已卸载');
    }

    this.inputNode = node;
  };

  render() {
    return (
      <div>
        <input ref={this.handleInputRef} type="text" />
      </div>
    );
  }
}

export default CallbackRefDemo;

自定义 Ref 对象的价值在于扩展,但也要避免过度设计。如果只是为了拿一个输入框节点,额外封装 gettersetter 或闭包会增加理解成本。更稳妥的做法是:先判断 createRefuseRef 是否足够,只有当赋值过程确实需要副作用、日志、缓存或兼容旧框架时,才引入自定义 Ref

三、createRef 与自定义 Ref 的核心差异对比

从工程角度看,createRef 和自定义 Ref 的差异主要体现在创建方式、扩展能力、适用场景和函数组件适配上。createRef 的优势是标准、简单、可预测;自定义 Ref 的优势是灵活,可以把引用管理从取值扩展为过程控制。

对比维度createRef自定义 Ref 对象
创建方式调用 React 官方提供的 createRef() 方法开发者自行封装对象,必须包含 current 属性
可扩展性默认只暴露 current,扩展需要额外封装可通过 gettersetter、额外方法扩展赋值逻辑
适用场景常规获取 DOM 节点或类组件实例需要监听赋值、记录日志、缓存引用、触发回调
函数组件适配函数组件中可以在渲染中调用,但会每次创建新对象,通常改用 useRef函数组件中也可以自定义,但需自行保证引用稳定

在场景选择上,可以先按需求复杂度分层判断。如果目标只是稳定拿到节点或实例,优先选择官方简单方案;如果目标是在赋值过程中加入行为,再考虑自定义方案。

优先选择 createRef 的场景

  • 只需要获取 DOM 节点或类组件实例,不需要额外的监听或扩展逻辑。
  • 在类组件中使用 ref,并且希望写法符合官方推荐。
  • 项目强调可读性和维护成本,不需要自定义 ref 相关逻辑。

优先选择自定义 Ref 对象的场景

  • 需要在 ref 被赋值或清空时执行日志、回调、校验等额外逻辑。
  • 需要给 ref 对象添加额外属性或方法,扩展引用管理能力。
  • 使用回调函数形式的 ref,需要更灵活的挂载和卸载处理。
  • 需要兼容旧版本 React,或有特殊的上层框架集成需求。

无论选择哪种方式,都要遵守 ref 的基本使用约束。

  • 普通 ref 不能直接用于函数组件实例,因为函数组件没有实例;如果希望函数组件暴露内部节点,应使用 forwardRef 或在内部使用 useRef 管理。
  • 不要在渲染过程中读取或写入 current,推荐在事件处理函数、生命周期方法或 useEffect 中访问。
  • 自定义 Ref 必须保留 current 属性,否则 React 无法完成挂载赋值。
  • 函数组件中如果需要使用类似 createRef 的能力,官方推荐使用 useRef,它返回的对象在组件生命周期内保持稳定。

函数组件中使用 useRef 时,通常把 ref 的读取放在 useEffect 中,这样可以避免在渲染阶段访问 DOM,也符合副作用执行时机。

import React, { useRef, useEffect } from 'react';

function FunctionRefDemo() {
  // useRef 返回的对象在组件生命周期内保持稳定
  const inputRef = useRef(null);

  useEffect(() => {
    // 在副作用中读取 ref,避免渲染阶段访问 DOM
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" />
    </div>
  );
}

export default FunctionRefDemo;

四、总结与延伸建议

综合来看,createRef 是基础且稳妥的选择,适合只需要稳定引用的场景;自定义 Ref 对象是扩展型选择,适合需要在引用赋值过程中加入额外行为的场景。实际项目中,应优先使用官方提供的简单方案,只有在明确需求驱动下才封装自定义 Ref

延伸建议是:在引入自定义 Ref 前,先明确它要解决的问题,例如日志、缓存、回调、兼容旧框架或测试替换;其次保证 ref 对象在组件生命周期内稳定;最后把 ref 的读写放在副作用或事件处理中,避免渲染阶段访问 DOM。先让 ref 保持最小可用,再按需求扩展,这样既能保持组件行为可预测,也能在需要扩展时拥有足够空间。

ReactcreateRef自定义Ref对象ref使用场景修改时间:2026-07-12 17:57:20

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。