🔍 React 面试官眼中的"秘密武器":深度剖析 useRef
在 React 的面试战场上,useRef 绝对是那个看似不起眼,实则能决定你能否进入下一轮的"关键先生"。很多候选人能熟练使用 useState 和 useEffect,但当被问及"如何在不触发重渲染的情况下保存数据"或"为什么我的定时器关不掉"时,往往哑口无言。
今天,我们就通过两段典型的实战代码,揭开 useRef 的神秘面纱,让你在面试中不仅能写出代码,更能讲出原理。
📌 核心考点:useRef 是什么?
在深入代码前,你需要向面试官传达两个核心概念:
- 它是一个"盒子" :
useRef返回一个普通的 JavaScript 对象,这个对象在组件的整个生命周期内保持引用稳定(始终是同一个对象)。 - 它是"非响应式"的 :修改
.current属性不会 触发组件的重新渲染。这是它与useState最根本的区别。
💡 场景一:DOM 的直接掌控者
面试官提问: "如何在组件挂载后,让输入框自动获得焦点?"
这通常是考察 useRef 最经典的入门题。我们来看第一段代码:
javascript
import { useEffect, useRef, useState } from "react"
export default function App() {
const [count, setCount] = useState(0);
console.log('变了'); // 每次count变化,组件重渲染,这句都会执行
const inputRef = useRef(null); // 创建一个ref容器
useEffect(() => {
// 在这里操作DOM
inputRef.current.focus(); // 让input获得焦点
console.log('222', inputRef.current); // 打印真实的DOM节点
}, [])
// 注意看这里:在渲染函数体中,inputRef.current 是 null
console.log('111', inputRef.current);
return (
<>
<input ref={inputRef}/> {/* 将ref绑定到DOM上 */}
<button type="button" onClick={() => setCount(count+1)}>count ++</button>
</>
)
}
面试解析要点:
- 同步与异步 :请留意代码中的
console.log。111:在函数体直接打印,此时 React 还在"画"界面,DOM 节点尚未生成,所以值为null。222:在useEffect中打印,此时 React 已经把界面"挂载"到页面上,inputRef.current已经被赋值为真实的 DOM 元素。
- 执行时机 :
useEffect在浏览器完成渲染后执行,这保证了我们操作的 DOM 是真实存在的。 - 价值体现 :这是
useRef最直观的用途------打破 React 的虚拟 DOM 抽象层,直接操作真实 DOM。
⏳ 场景二:跨越渲染的"持久化"存储
面试官提问: "React 函数组件每次渲染都会重新执行函数,如果我在一个定时器里需要保存状态,或者想在点击按钮时清除上一次的定时器,该怎么办?"
这时候,如果只用普通变量,每次重渲染变量都会被重置;如果用 useState,清除定时器的逻辑会变得复杂且容易出错。第二段代码展示了 useRef 的高级用法:
javascript
import { useRef, useState, useEffect } from 'react';
export default function App() {
let intervalId = useRef(null); // 用ref来保存定时器ID
const [count, setCount] = useState(0);
function start() {
// 即使组件多次渲染,intervalId 这个"盒子"始终是同一个
intervalId.current = setInterval(() => {
console.log('tick~~~');
}, 1000);
console.log(intervalId); // 打印 { current: 123 }
}
function stop() {
// 从"盒子"里取出ID并清除
clearInterval(intervalId.current);
}
// 仅用于演示:当count变化时,查看ref的值
useEffect(() => {
console.log(intervalId.current); // 只要定时器开着,这里会一直打印ID
}, [count])
return (
<>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
<button type="button" onClick={() => setCount(count+1)}>count ++</button>
</>
)
}
面试解析要点:
- 闭包陷阱的解药 :在
start函数中,我们将setInterval返回的 ID 存入了intervalId.current。无论组件因为count变化重渲染多少次,intervalId对象本身不会变,变的只是它里面的current值。 - 清理资源 :
stop函数能够准确获取到最新的定时器 ID 并清除它。如果不用useRef,而用普通变量,stop函数将无法访问到start函数内部的变量(作用域隔离)。 - 非响应式优势 :我们将定时器 ID 存入
ref,并不希望它触发页面刷新。useRef完美地充当了一个"默默奉献的存储柜"角色。
📝 总结:面试官想听到什么?
当被问及 useRef 时,请务必构建以下回答框架:
- 定义:它是用来创建一个在组件生命周期内持久化且引用稳定的对象。
- 两大用途 :
- 访问 DOM :通过
ref属性附加到元素上。 - 存储可变值:存储定时器 ID、上一次的 props、第三方库实例等不需要触发视图更新的数据。
- 访问 DOM :通过
- 与
useState的区别 :ref的变化是同步的且不触发重渲染;state的变化是异步的且一定会触发重渲染。 - 避坑指南 :不要试图用
useRef替代状态管理,因为它的变化 React "看不见",UI 不会自动更新。
掌握这些,你就能在面试中自信地告诉面试官:useRef** 不仅仅是一个获取 DOM 的工具,它是连接函数组件渲染周期与可变状态的桥梁。**