- React.forwardRef 实战代码示例
- 包含 4 个常见使用场景
示例 1:受控输入框
typescript
复制代码
import React, {
forwardRef,
useImperativeHandle,
useRef,
useState,
} from "react";
/**
* 自定义输入框组件
* - 暴露 focus、blur、clear、getValue 等方法
* - 父组件可以直接控制输入框
*/
interface CustomInputHandle {
focus: () => void;
blur: () => void;
clear: () => void;
getValue: () => string;
setValue: (value: string) => void;
}
interface CustomInputProps {
placeholder?: string;
defaultValue?: string;
}
export const CustomInput = forwardRef<CustomInputHandle, CustomInputProps>(
(props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
blur: () => inputRef.current?.blur(),
clear: () => {
if (inputRef.current) {
inputRef.current.value = "";
}
},
getValue: () => inputRef.current?.value || "",
setValue: (value: string) => {
if (inputRef.current) {
inputRef.current.value = value;
}
},
}));
return (
<input
ref={inputRef}
placeholder={props.placeholder}
defaultValue={props.defaultValue}
style={{
padding: "8px 12px",
border: "1px solid #d9d9d9",
borderRadius: "4px",
}}
/>
);
}
);
CustomInput.displayName = "CustomInput";
示例 2:可伸缩面板
ini
复制代码
/**
* 可伸缩面板组件
* - 通过 ref 调用 expand/collapse 方法
* - 适合 Accordion、Collapse 等组件
*/
interface CollapseHandle {
expand: () => void;
collapse: () => void;
toggle: () => void;
isExpanded: () => boolean;
}
interface CollapseProps {
title: string;
children: React.ReactNode;
}
export const Collapse = forwardRef<CollapseHandle, CollapseProps>(
({ title, children }, ref) => {
const contentRef = useRef<HTMLDivElement>(null);
const [isExpanded, setIsExpanded] = useState(false);
useImperativeHandle(ref, () => ({
expand: () => {
setIsExpanded(true);
if (contentRef.current) {
contentRef.current.style.height = "auto";
contentRef.current.style.overflow = "visible";
}
},
collapse: () => {
setIsExpanded(false);
if (contentRef.current) {
contentRef.current.style.height = "0";
contentRef.current.style.overflow = "hidden";
}
},
toggle: () => {
if (isExpanded) {
if (contentRef.current) {
contentRef.current.style.height = "0";
contentRef.current.style.overflow = "hidden";
}
setIsExpanded(false);
} else {
if (contentRef.current) {
contentRef.current.style.height = "auto";
contentRef.current.style.overflow = "visible";
}
setIsExpanded(true);
}
},
isExpanded: () => isExpanded,
}));
return (
<div style={{ border: "1px solid #ddd", marginBottom: "8px" }}>
<div
onClick={() => {
setIsExpanded(!isExpanded);
}}
style={{
padding: "12px",
cursor: "pointer",
backgroundColor: "#f5f5f5",
}}
>
{title}
</div>
<div
ref={contentRef}
style={{
height: isExpanded ? "auto" : "0",
overflow: "hidden",
transition: "all 0.3s ease",
}}
>
<div style={{ padding: "12px" }}>{children}</div>
</div>
</div>
);
}
);
Collapse.displayName = "Collapse";
示例 3:视频播放器
typescript
复制代码
/**
* 视频播放器组件
* - 暴露播放、暂停、跳转等控制方法
* - 父组件可以构建自定义播放器控制条
*/
interface VideoPlayerHandle {
play: () => void;
pause: () => void;
seek: (time: number) => void;
getCurrentTime: () => number;
getDuration: () => number;
setVolume: (volume: number) => void;
}
interface VideoPlayerProps {
src: string;
autoplay?: boolean;
muted?: boolean;
}
export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
(props, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);
useImperativeHandle(ref, () => ({
play: () => videoRef.current?.play(),
pause: () => videoRef.current?.pause(),
seek: (time: number) => {
if (videoRef.current) {
videoRef.current.currentTime = time;
}
},
getCurrentTime: () => videoRef.current?.currentTime || 0,
getDuration: () => videoRef.current?.duration || 0,
setVolume: (volume: number) => {
if (videoRef.current) {
videoRef.current.volume = volume;
}
},
}));
return (
<video
ref={videoRef}
src={props.src}
autoPlay={props.autoplay}
muted={props.muted}
style={{
width: "100%",
maxWidth: "600px",
backgroundColor: "#000",
}}
/>
);
}
);
VideoPlayer.displayName = "VideoPlayer";
示例 4:Modal 对话框
less
复制代码
/**
* Modal 组件
* - 暴露 open/close 方法
* - 父组件可以完全控制打开/关闭状态
*/
interface ModalHandle {
open: () => void;
close: () => void;
toggle: () => void;
isOpen: () => boolean;
}
interface ModalProps {
title: string;
children: React.ReactNode;
onClose?: () => void;
}
export const Modal = forwardRef<ModalHandle, ModalProps>(
({ title, children, onClose }, ref) => {
const [isVisible, setIsVisible] = useState(false);
useImperativeHandle(ref, () => ({
open: () => setIsVisible(true),
close: () => {
setIsVisible(false);
onClose?.();
},
toggle: () => setIsVisible(!isVisible),
isOpen: () => isVisible,
}));
if (!isVisible) return null;
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.5)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 1000,
}}
>
<div
style={{
backgroundColor: "white",
padding: "20px",
borderRadius: "8px",
minWidth: "400px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)",
}}
>
<h2>{title}</h2>
<div style={{ margin: "16px" }}>{children}</div>
<button
onClick={() => {
setIsVisible(false);
onClose?.();
}}
style={{
padding: "8px 16px",
backgroundColor: "#1890ff",
color: "white",
border: "none",
borderRadius: "4px",
cursor: "pointer",
}}
>
Close
</button>
</div>
</div>
);
}
);
Modal.displayName = "Modal";
使用示例
javascript
复制代码
/**
* 完整的使用示例
*/
export const ForwardRefExamples = () => {
const customInputRef = useRef<CustomInputHandle>(null);
const collapseRef = useRef<CollapseHandle>(null);
const videoRef = useRef<VideoPlayerHandle>(null);
const modalRef = useRef<ModalHandle>(null);
return (
<div style={{ padding: "20px" }}>
<h1>React.forwardRef 示例</h1>
{/* 示例 1:输入框 */}
<section>
<h2>示例 1:受控输入框</h2>
<CustomInput ref={customInputRef} placeholder="Enter text" />
<div style={{ marginTop: "8px" }}>
<button onClick={() => customInputRef.current?.focus()}>Focus</button>
<button onClick={() => customInputRef.current?.clear()}>Clear</button>
<button
onClick={() => {
const value = customInputRef.current?.getValue();
alert(`Value: ${value}`);
}}
>
Get Value
</button>
<button onClick={() => customInputRef.current?.setValue("Hello")}>
Set Value
</button>
</div>
</section>
{/* 示例 2:可伸缩面板 */}
<section>
<h2>示例 2:可伸缩面板</h2>
<Collapse ref={collapseRef} title="Click to expand">
<p>This is the content of the collapse panel.</p>
</Collapse>
<div>
<button onClick={() => collapseRef.current?.expand()}>Expand</button>
<button onClick={() => collapseRef.current?.collapse()}>
Collapse
</button>
<button onClick={() => collapseRef.current?.toggle()}>Toggle</button>
</div>
</section>
{/* 示例 3:视频播放器 */}
<section>
<h2>示例 3:视频播放器</h2>
<VideoPlayer
ref={videoRef}
src="https://www.w3schools.com/html/mov_bbb.mp4"
/>
<div style={{ marginTop: "8px" }}>
<button onClick={() => videoRef.current?.play()}>Play</button>
<button onClick={() => videoRef.current?.pause()}>Pause</button>
<button onClick={() => videoRef.current?.seek(10)}>Jump 10s</button>
<button
onClick={() => {
const time = videoRef.current?.getCurrentTime();
alert(`Current time: ${time?.toFixed(2)}s`);
}}
>
Show Time
</button>
</div>
</section>
{/* 示例 4:Modal 对话框 */}
<section>
<h2>示例 4:Modal 对话框</h2>
<button onClick={() => modalRef.current?.open()}>Open Modal</button>
<Modal
ref={modalRef}
title="Confirm Action"
onClose={() => console.log("Modal closed")}
>
<p>Are you sure you want to continue?</p>
</Modal>
</section>
</div>
);
};
export default ForwardRefExamples;