React 中 ref 回调函数接收 null 的原因解析

在 react 中,ref 回调函数(如 `ref={el => {...}}`)会在 dom 元素挂载时传入真实节点,卸载时传入 `null`,这是 react 内置的生命周期行为,用于确保 ref 始终准确反映 dom 状态。

当你使用函数式 ref(即 ref={el => inputsRef.current[i] = el})时,React 会在两个关键时机调用该回调

  • 挂载阶段:DOM 元素首次插入文档后,React 传入对应的 HTMLInputElement 实例;
  • 卸载阶段:当该元素被移除(例如数组长度变化、条件渲染导致组件消失、父组件重渲染等),React 会再次调用该回调,并传入 null,以显式通知你“这个 ref 已失效”。

这就是为什么 el 类型为 HTMLInputElement | null —— TypeScript 正确地反映了这一双向生命周期语义。

正确处理 null 的推荐写法

export default function SomeArray() {
  const inputsRef = useRef([]);

  return (
    <>
      {[1, 2, 3].map((_, i) => (
         {
            if (el) {
              // 挂载:安全赋值
              inputsRef.current[i] = el;
            } else {
              // 卸载:清理引用,防止内存泄漏或 stale node
              inputsRef.current[i] = null;
            }
          }}
        />
      ))}
    
  );
}

关键注意事项

  • 必须添加 key:若省略 key,React 可能复用 DOM 节点,导致 ref 回调被错误触发(例如旧索引收到 null,新索引未及时更新),引发 inputsRef.current 数据错乱;
  • 初始化 ref 数组需谨慎:useRef([]) 是安全的,但不要预先填充 null(如 new Array(3).fill(null)),因为 ref 回调本身已负责状态同步;
  • 避免直接解构或读取未挂载项:访问 inputsRef.current[i] 前应做空值检查(如 if (inputsRef.current[i]) { ...focus() }),尤其在副作用(如 useEffect)中;
  • 替代方案考虑:对动态列表,也可用 useRef>({}) 或结合 useCallback + createRef(),但函数式 ref 仍是标准且最可控的方式。

简言之,null 不是类型缺陷,而是 React 精确控制 DOM 生命周期的契约体现——尊重它,才能写出健壮、可预测的 ref 管理逻辑。