React 之 useRef 学习
·
前情提要
useRef 是一个特殊的 Hook,它的值在组件重新渲染时不会被重新创建,这正是它的核心特性。
useRef 的特点:
- 在组件重新渲染时保持引用不变
- 修改
.current不会触发重新渲染 - 适合存储不需要触发渲染的可变值
使用场景:
- 访问和操作 DOM 元素
- 保存定时器、订阅等需要清理的资源
- 缓存前一次的值
- 避免闭包陷阱
- 性能优化 - 缓存昂贵对象
- 存储任何不需要触发渲染的可变数据
useRef 的渲染行为
function MyComponent() {
const [count, setCount] = useState(0);
// ✅ useRef 只在组件首次挂载时创建,后续渲染不会重新创建
const countRef = useRef(0);
const inputRef = useRef(null);
// ❌ 普通变量每次渲染都会重新创建
const normalVariable = { value: 0 };
console.log('组件渲染了');
console.log('countRef 是否是同一个对象:', countRef); // 始终是同一个对象
console.log('normalVariable:', normalVariable); // 每次都是新对象
const handleClick = () => {
setCount(count + 1); // 触发重新渲染
countRef.current += 1; // 不会触发重新渲染
};
return (
<div>
<p>State count: {count}</p>
<p>Ref count: {countRef.current}</p>
<button onClick={handleClick}>增加</button>
<input ref={inputRef} />
</div>
);
}
什么时候使用 useRef
1. 访问 DOM 元素
function MyComponent() {
const [count, setCount] = useState(0);
// ✅ useRef 只在组件首次挂载时创建,后续渲染不会重新创建
const countRef = useRef(0);
const inputRef = useRef(null);
// ❌ 普通变量每次渲染都会重新创建
const normalVariable = { value: 0 };
console.log('组件渲染了');
console.log('countRef 是否是同一个对象:', countRef); // 始终是同一个对象
console.log('normalVariable:', normalVariable); // 每次都是新对象
const handleClick = () => {
setCount(count + 1); // 触发重新渲染
countRef.current += 1; // 不会触发重新渲染
};
return (
<div>
<p>State count: {count}</p>
<p>Ref count: {countRef.current}</p>
<button onClick={handleClick}>增加</button>
<input ref={inputRef} />
</div>
);
}
2. 保存可变值(不触发重新渲染)
function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
// 保存定时器 ID,清除时需要用到
intervalRef.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
useEffect(() => {
return () => {
// 组件卸载时清除定时器
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);
return (
<div>
<p>时间: {seconds}秒</p>
<button onClick={startTimer}>开始</button>
<button onClick={stopTimer}>停止</button>
</div>
);
}
3. 保存前一次的值
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function MyComponent() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>当前值: {count}</p>
<p>前一次的值: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
4. 避免闭包陷阱
function ChatRoom() {
const [message, setMessage] = useState('');
const latestMessageRef = useRef('');
// 更新 ref 以保持最新值
latestMessageRef.current = message;
useEffect(() => {
const timer = setInterval(() => {
// 使用 ref 获取最新值,避免闭包陷阱
console.log('最新消息:', latestMessageRef.current);
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖数组,但仍能获取最新值
return (
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="输入消息"
/>
);
}
5. 性能优化 - 缓存昂贵的对象
function ExpensiveComponent() {
const [count, setCount] = useState(0);
// ✅ 使用 useRef 缓存昂贵的对象创建
const expensiveObjectRef = useRef(null);
if (!expensiveObjectRef.current) {
expensiveObjectRef.current = new SomeExpensiveClass();
}
// ❌ 每次渲染都会重新创建(性能问题)
// const expensiveObject = new SomeExpensiveClass();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
6. 强制更新组件
function useForceUpdate() {
const [, setTick] = useState(0);
const update = useCallback(() => {
setTick(tick => tick + 1);
}, []);
return update;
}
// 或者使用 useRef
function useForceUpdateWithRef() {
const forceUpdate = useRef(() => {});
const [, setState] = useState({});
forceUpdate.current = () => setState({});
return forceUpdate.current;
}
useRef vs useState 对比
function Comparison() {
const [stateValue, setStateValue] = useState(0);
const refValue = useRef(0);
const updateState = () => {
setStateValue(prev => prev + 1); // ✅ 触发重新渲染
};
const updateRef = () => {
refValue.current += 1; // ❌ 不触发重新渲染
console.log('Ref value:', refValue.current);
};
return (
<div>
<p>State: {stateValue}</p>
<p>Ref: {refValue.current}</p>
<button onClick={updateState}>更新 State</button>
<button onClick={updateRef}>更新 Ref</button>
</div>
);
}
更多推荐


所有评论(0)