前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库React 19 ref callback cleanup 清理函数
ReReactHooks 与复用

React 19 的 ref 回调清理函数怎样工作,为什么内联回调也会触发清理?

清理函数把本次节点绑定获得的资源留在同一个闭包里,释放时不必再猜它属于哪次关联。

前端进阶之旅 · 一题精讲更新于 2026.09.06
React#Hooks 与复用
先看核心答案读代码示例
理解线索

每次关联拥有自己的资源闭包

  1. 获得节点创建本轮监听函数并绑定资源
  2. 返回清理捕获同一节点和同一监听函数
  3. 关联结束释放旧资源,再允许后续重新建立

下面使用原生事件只是为了展示清理配对,普通 React 点击业务通常直接用 onClick 更简单。

核心回答

先记住这个答案

React 19 允许 ref 回调返回一个清理函数,React 在移除该关联时调用它,例如节点卸载或 ref 回调换成新的函数。清理闭包可以直接保存本轮节点、监听函数和其他资源,形成明确的一次建立与对应释放。提供清理函数后,React 会使用它,而不是再以 null 调用该回调作为同一次清理;没有返回清理函数的旧式 null 处理仍有兼容行为。开发 Strict Mode 还会增加建立与清理检查,代码必须能够正确重建,不能假设回调只运行一次。

  • 清理保存本轮节点与资源,避免错删新关联
  • 回调身份变化也会先清理旧绑定
  • 返回值应是清理函数或没有返回值

把监听函数保存在本次绑定的清理闭包里

如果释放时使用另一个新函数,removeEventListener 无法删除最初的监听。清理函数和建立逻辑写在一起,可以保留正确身份;同时它捕获的是本轮节点,不会误用某个已经指向新节点的外部 current。

示例的 label 变化会产生新的 attach 函数,因此即使按钮节点保留,也会先移除旧标签对应的监听再建立新监听。点击日志应使用当前标签,不能让旧监听继续存在而一次点击打印两种标签。

React 19 ref 清理绑定本轮节点TSX
import { useCallback } from 'react';

type Props = { label: string };
export default function NativeNotice({ label }: Props) {
  const attach = useCallback((node: HTMLButtonElement | null) => {
    if (!node) return;
    const notify = () => console.log('native', label);
    node.addEventListener('click', notify);
    console.log('attach', label);
    return () => {
      node.removeEventListener('click', notify);
      console.log('cleanup', label);
    };
  }, [label]);
  return <button ref={attach}>通知:{label}</button>;
}

从标签 A 改为 B 时,旧监听被清理后重新绑定;节点卸载时释放当前监听。日志用于展示关联配对,不应把开发检查造成的额外建立与清理当成业务通知已经发送。

迁移时注意以前被忽略的返回值

旧代码常写成 node => map.set(id, node),表达式会隐式返回 Map 实例。React 19 的 ref 返回值有明确清理含义,类型检查会指出这类意外返回;应改为块体完成登记,并显式返回正确的删除逻辑或不返回值。

不能把业务处理结果、布尔值或节点本身当成清理函数返回。若共享组件还要支持旧 React,需要按支持矩阵设计兼容实现并分别测试,不能在 React 18 环境里期待新版本的清理函数契约自动生效。

重复建立暴露的是资源配对问题

Strict Mode 的额外检查会让缺失清理更容易暴露,正确实现应在检查后仍只有当前有效监听。不要为了让日志看起来只出现一次而取消清理;那会让真实卸载、条件分支和回调替换留下资源。

更复杂的观察器或第三方实例也遵循同样原则:本轮建立获得什么资源,清理就释放对应资源。若资源具有异步关闭过程,还需要单独定义旧实例与新实例交接策略,React 调用清理不等于任意外部系统都已立即完成关闭。

回答前,多想一步

容易答错的地方

返回清理函数后还会自动收到一次 null 作为同次清理
React 19 会使用返回的清理函数完成该关联的释放;不能把两条路径都当成必定执行,否则清理计数和兼容逻辑容易重复。
useCallback 依赖为空就能让任何资源只建立一次
真实节点可能移除重建,开发检查也可能再次关联;而回调读取的配置若变化,应正确更新依赖,不能靠空数组保存过期配置。
试着用自己的话回答

面试官还会怎么问?

只更新父组件为什么有时也会触发 ref 清理?

如果父层每次创建新的 ref 函数,它的身份变化会导致旧关联清理和新关联建立。先检查回调身份,再判断是否真的替换了 DOM 节点。

清理应该从最新 ref.current 读取节点吗?

通常直接捕获建立时得到的节点更准确。最新 current 可能已经指向另一个关联,使用它释放旧资源会清错对象或遗漏旧监听。

可以把所有 onClick 都改成 ref 内的监听吗?

通常没有必要,React 事件属性已经表达普通点击业务。ref 资源绑定主要用于确实需要命令式集成的接口,否则会增加手动清理和维护成本。

从一道题,走向一组知识

把知识连起来

Hooks 与复用

React 回调 ref 适合什么场景,条件 DOM 出现后为什么不一定需要 Effect?

先理解节点关联与对象 ref 存储的区别

副作用与同步

React Effect 挂载、依赖更新和卸载时怎样建立与清理?

比较 Effect 资源生命周期中的建立与清理顺序

参考资料

  • React 19:Cleanup functions for refs
  • React:ref callback

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 把监听函数保存在本次绑定的清理闭包里
  3. 迁移时注意以前被忽略的返回值
  4. 重复建立暴露的是资源配对问题
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑