React.forwardRef 实战代码示例

  • 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;
相关推荐
朝歌青年说5 小时前
一个在多年的技术债项目中写出来的miniHMR热更新工具
前端
Moonbit5 小时前
倒计时 2 天|Meetup 议题已公开,Copilot 月卡等你来拿!
前端·后端
Glink5 小时前
现在开始将Github作为数据库
前端·算法·github
小仙女喂得猪5 小时前
2025 跨平台方案KMP,Flutter,RN之间的一些对比
android·前端·kotlin
举个栗子dhy5 小时前
第二章、全局配置项目主题色(主题切换+跟随系统)
前端·javascript·react.js
sorryhc6 小时前
开源的SSR框架都是怎么实现的?
前端·javascript·架构
前端架构师-老李6 小时前
npm、yarn、pnpm的对比和优略
前端·npm·node.js·pnpm·yarn
fox_6 小时前
别再混淆 call/apply/bind 了!一篇讲透用法、场景与手写逻辑(二)
前端·javascript
潜心编码6 小时前
基于vue的停车场管理系统
前端·javascript·vue.js