You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React DOM元素每次重渲染触发挂载卸载而非更新的原因问询

问题原因解答

你观察到的callback ref每次渲染都被调用的现象,并不代表table对应的DOM节点真的被卸载重建,和React协调的同类型节点复用规则没有冲突,本质是你对callback ref的调用触发场景理解不全,加上内联函数的引用特性共同导致的。


核心规则补充

你之前参考的官方文档只说明了两个最基础的ref调用时机:

组件挂载时,React会向ref回调传入对应DOM元素进行调用;组件卸载时,会传入null调用该ref回调。

但文档同时隐含了一个非常关键的触发场景,很多开发者都会忽略:

如果两次渲染间传入的ref回调函数本身的引用发生了变化,React会先传入null调用上一次渲染的旧ref回调做清理,再传入当前DOM节点调用新一次渲染的新ref回调。

你的代码里给table传的ref是直接写在JSX里的内联箭头函数:

<table ref={(ref) => console.log(ref)}>

每次组件因为num状态变化触发重渲染时,这个箭头函数都会被重新创建,生成一个全新的函数引用,和上一次渲染用到的ref回调完全不是同一个函数。React检测到ref属性的引用变更,就会按照规则先执行旧回调的清理逻辑(传null),再执行新回调传入当前的table DOM节点,所以你每次点击按钮都会看到两条日志:先输出null,再输出table节点,很容易误以为DOM被卸载重建。

验证方式

你可以通过简单的对比确认DOM节点从未被替换:在ref回调里把拿到的DOM节点存在一个外层变量里,下次回调拿到新的节点时做全等对比,就会发现每次传入的table节点都是同一个引用,底层DOM完全没有走卸载、重挂载的流程,只是ref回调被替换触发了调用而已。

避免重复触发的方案

如果你不希望每次重渲染都触发ref回调,只需要保证ref回调的引用在多次渲染间保持稳定即可,有两种常用实现方式:

  • 用useCallback包裹ref回调,设置空依赖数组保证引用不变:
    const stableTableRef = useCallback((node) => {
      console.log(node);
    }, []);
    
    // JSX中使用
    <table ref={stableTableRef}>
    
    这种写法下,ref回调只会在组件首次挂载时传入DOM节点调用一次,组件卸载时传入null调用一次,中间的重渲染不会触发回调。
  • 直接使用useRef创建ref对象替代callback ref,ref对象本身的引用是永久稳定的,不会出现重复触发的问题:
    const tableRef = useRef(null);
    // 需要访问DOM节点时直接取tableRef.current即可
    
    // JSX中使用
    <table ref={tableRef}>
    

常见误区提醒

不要把callback ref传入null的场景和DOM卸载直接划等号:只有当传入的ref回调是稳定引用时,收到null才代表对应的DOM节点真的被卸载;如果是每次渲染都重新创建的内联ref回调,收到null只是React执行新旧ref回调切换的清理流程,和DOM本身的生命周期无关。


内容的提问来源于stack exchange,提问作者fast-reflexes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 00:48:24