useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数。这个对象在组件的整个生命周期内保持不变。
import React, { useRef, useEffect } from 'react';
function TextInput() {
// 创建ref对象
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载后,inputRef.current指向真实的DOM元素
console.log('DOM元素:', inputRef.current);
console.log('元素值:', inputRef.current.value);
}, []);
const handleClick = () => {
// 访问DOM元素的属性和方法
inputRef.current.focus();
inputRef.current.style.backgroundColor = '#ffffcc';
};
return (
<div>
<input
ref={inputRef}
type="text"
defaultValue="初始文本"
/>
<button onClick={handleClick}>
聚焦并高亮
</button>
</div>
);
}
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载时自动聚焦
inputRef.current.focus();
}, []);
const handleClear = () => {
inputRef.current.value = '';
inputRef.current.focus();
};
return (
<div>
<input
ref={inputRef}
placeholder="输入内容..."
/>
<button onClick={handleClear}>清空</button>
</div>
);
}
function MeasureElement() {
const divRef = useRef(null);
const [dimensions, setDimensions] = useState({});
useEffect(() => {
if (divRef.current) {
const rect = divRef.current.getBoundingClientRect();
setDimensions({
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left
});
}
}, []);
return (
<div>
<div
ref={divRef}
style={{
padding: '20px',
border: '1px solid #ccc',
margin: '10px 0'
}}
>
测量这个元素
</div>
<div>
<p>宽度: {dimensions.width}px</p>
<p>高度: {dimensions.height}px</p>
</div>
</div>
);
}
function CounterWithRef() {
const countRef = useRef(0); // 不会触发重新渲染
const [count, setCount] = useState(0); // 会触发重新渲染
const incrementRef = () => {
countRef.current += 1;
console.log('Ref计数:', countRef.current);
};
const incrementState = () => {
setCount(count + 1);
};
return (
<div>
<div>
<h4>useState计数: {count}</h4>
<button onClick={incrementState}>增加State</button>
</div>
<div>
<h4>useRef计数: {countRef.current}</h4>
<button onClick={incrementRef}>增加Ref</button>
<p className="text-muted">
※ Ref变化不会触发重新渲染,所以显示的值不会更新
</p>
</div>
</div>
);
}
默认情况下,函数组件不能接收ref属性。使用forwardRef可以将ref传递给子组件。
import React, { forwardRef, useRef } from 'react';
// 子组件使用forwardRef包装
const FancyInput = forwardRef((props, ref) => {
return (
<input
ref={ref}
className="fancy-input"
{...props}
/>
);
});
// 父组件
function ParentComponent() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
inputRef.current.select();
};
return (
<div>
<FancyInput
ref={inputRef}
placeholder="我是fancy input"
/>
<button onClick={handleFocus}>
聚焦并选中文本
</button>
</div>
);
}
使用useImperativeHandle可以自定义通过ref暴露给父组件的方法。
import React, {
forwardRef,
useRef,
useImperativeHandle
} from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
// 自定义暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
shake: () => {
const input = inputRef.current;
input.style.transform = 'translateX(10px)';
setTimeout(() => {
input.style.transform = 'translateX(-10px)';
setTimeout(() => {
input.style.transform = '';
}, 50);
}, 50);
},
getValue: () => {
return inputRef.current.value;
},
setValue: (value) => {
inputRef.current.value = value;
}
}));
return <input ref={inputRef} {...props} />;
});
function ParentComponent() {
const customInputRef = useRef(null);
return (
<div>
<CustomInput
ref={customInputRef}
placeholder="试试特殊效果"
/>
<div className="control-panel">
<button onClick={() => customInputRef.current.focus()}>
聚焦
</button>
<button onClick={() => customInputRef.current.shake()}>
摇动效果
</button>
<button onClick={() => console.log(customInputRef.current.getValue())}>
获取值
</button>
</div>
</div>
);
}
function TimerComponent() {
const [time, setTime] = useState(0);
const timerRef = useRef(null);
const startTimer = () => {
if (!timerRef.current) {
timerRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
}
};
const stopTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
const resetTimer = () => {
stopTimer();
setTime(0);
};
useEffect(() => {
// 组件卸载时清理定时器
return () => stopTimer();
}, []);
return (
<div>
<h3>计时器: {time}秒</h3>
<button onClick={startTimer}>开始</button>
<button onClick={stopTimer}>停止</button>
<button onClick={resetTimer}>重置</button>
</div>
);
}
function UsePreviousValue() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
// 更新ref为当前值
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<div>
<p>当前计数: {count}</p>
<p>上一次计数: {prevCount !== undefined ? prevCount : '无'}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}
function VideoPlayer() {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const togglePlay = () => {
if (videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
}
};
const skip = (seconds) => {
if (videoRef.current) {
videoRef.current.currentTime += seconds;
}
};
return (
<div>
<video
ref={videoRef}
width="400"
src="/path/to/video.mp4"
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
/>
<div className="control-panel">
<button onClick={togglePlay}>
{isPlaying ? '暂停' : '播放'}
</button>
<button onClick={() => skip(-10)}>
-10秒
</button>
<button onClick={() => skip(10)}>
+10秒
</button>
<button onClick={() => videoRef.current.volume = 0}>
静音
</button>
</div>
</div>
);
}
function RefCallbackExample() {
const [height, setHeight] = useState(0);
// Ref回调函数
const measureRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div>
<div
ref={measureRef}
style={{ padding: '20px', background: '#e3f2fd' }}
>
这个元素的高度会被测量
</div>
<p>元素高度: {height}px</p>
</div>
);
}
| 特性 | useRef | createRef |
|---|---|---|
| 使用场景 | 函数组件 | 类组件 |
| 生命周期 | 在整个组件生命周期中保持不变 | 每次渲染创建新引用 |
| 性能 | 更高效,避免重复创建 | 每次渲染都会创建新对象 |
| 存储值 | 可以存储任意值并保持更新 | 主要用于DOM引用 |
| 重新渲染 | 变化不会触发重新渲染 | 每次渲染创建新引用 |
const inputRef = useRef<HTMLInputElement>(null);
这个示例展示了如何结合使用useRef、useState和DOM操作:
function EditableList() {
const [items, setItems] = useState(['苹果', '香蕉', '橙子']);
const inputRef = useRef(null);
const listRef = useRef(null);
const addItem = () => {
const value = inputRef.current.value.trim();
if (value) {
setItems([...items, value]);
inputRef.current.value = '';
inputRef.current.focus();
}
};
const removeItem = (index) => {
const newItems = items.filter((_, i) => i !== index);
setItems(newItems);
};
useEffect(() => {
// 每次列表更新后滚动到底部
if (listRef.current) {
listRef.current.scrollTop = listRef.current.scrollHeight;
}
}, [items]);
return (
<div>
<div>
<input ref={inputRef} placeholder="输入新项目..." />
<button onClick={addItem}>添加</button>
</div>
<ul
ref={listRef}
style={{
maxHeight: '200px',
overflowY: 'auto',
padding: '10px',
border: '1px solid #ddd'
}}
>
{items.map((item, index) => (
<li key={index} style={{ margin: '5px 0' }}>
{item}
<button
onClick={() => removeItem(index)}
style={{ marginLeft: '10px' }}
>
删除
</button>
</li>
))}
</ul>
</div>
);
}