You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中如何使用forwardRef为多个Ref添加TypeScript类型?

解决React+TypeScript中多Ref传递的类型错误问题

问题场景

父组件Home定义多个DOM ref,组合成对象传递给各section子组件,子组件内部使用对应ref绑定自身容器,同时通过按钮触发滚动到下一个section。功能正常,但存在TypeScript类型错误。

父组件代码

const Home: React.FC = () => {
  const effectiveCoursesRef = useRef(null);
  const reviewsRef = useRef(null);
  const howWorksRef = useRef(null);
  const scienceBehindRef = useRef(null);
  const FAQRef = useRef(null);

  const refObject = {
    effectiveCoursesRef,
    reviewsRef,
    howWorksRef,
    scienceBehindRef,
    FAQRef,
  };
  
  return (
    <MainLayout>
      <main>
        <EffectiveCourses ref={refObject} />
        <Reviews ref={refObject} />
        <HowSleepSchoolWork ref={refObject} />
        <ScienceBehind ref={refObject} />
        <FAQ ref={refObject} />
      </main>
    </MainLayout>
  );
};

export default Home;

子组件(ScienceBehind)代码

const ScienceBehind = forwardRef((_, ref: any) => (
  <section className={`${styles.section}`} ref={ref.scienceBehindRef}>
    <div className="container">
      <h2 className={styles.sectionTitle}>Science behind Sleep School</h2>
      <ArrowButton ref={ref.FAQRef} color="#082B4B" />
    </div>
  </section>
));

export default ScienceBehind;

解决方案

方案一:修正TypeScript类型定义

通过显式定义ref对象类型、指定useRef泛型参数,解决类型不匹配问题:

  1. 定义统一的ref类型
// 定义所有section ref的类型
type SectionRefs = {
  effectiveCoursesRef: React.RefObject<HTMLElement>;
  reviewsRef: React.RefObject<HTMLElement>;
  howWorksRef: React.RefObject<HTMLElement>;
  scienceBehindRef: React.RefObject<HTMLElement>;
  FAQRef: React.RefObject<HTMLElement>;
};
  1. 父组件中指定useRef的泛型参数
const Home: React.FC = () => {
  // 给每个ref指定HTMLElement类型,避免默认的null类型
  const effectiveCoursesRef = useRef<HTMLElement>(null);
  const reviewsRef = useRef<HTMLElement>(null);
  const howWorksRef = useRef<HTMLElement>(null);
  const scienceBehindRef = useRef<HTMLElement>(null);
  const FAQRef = useRef<HTMLElement>(null);

  // 显式指定refObject的类型(也可让TS自动推断)
  const refObject: SectionRefs = {
    effectiveCoursesRef,
    reviewsRef,
    howWorksRef,
    scienceBehindRef,
    FAQRef,
  };
  
  return (
    <MainLayout>
      <main>
        <EffectiveCourses ref={refObject} />
        <Reviews ref={refObject} />
        <HowSleepSchoolWork ref={refObject} />
        <ScienceBehind ref={refObject} />
        <FAQ ref={refObject} />
      </main>
    </MainLayout>
  );
};
  1. 子组件中指定forwardRef的泛型参数
// 给forwardRef指定泛型:第一个是ref类型,第二个是组件props类型(这里为空)
const ScienceBehind = forwardRef<SectionRefs, {}>((_, ref) => (
  <section className={`${styles.section}`} ref={ref.scienceBehindRef}>
    <div className="container">
      <h2 className={styles.sectionTitle}>Science behind Sleep School</h2>
      <ArrowButton ref={ref.FAQRef} color="#082B4B" />
    </div>
  </section>
));

注意:如果ArrowButton也是通过forwardRef接收ref,需要给它也定义正确的类型(比如React.RefObject<HTMLButtonElement>)。


方案二:更优实现(降低耦合)

将滚动逻辑封装为自定义Hook,子组件无需接收完整的ref对象,只需接收滚动触发方法,代码更清晰、耦合度更低:

  1. 自定义Hook:useScrollToSection
import { useRef, useCallback } from 'react';

// 定义所有section的key类型
type SectionKey = 'effectiveCourses' | 'reviews' | 'howWorks' | 'scienceBehind' | 'FAQ';

export const useScrollToSection = () => {
  // 集中管理所有section的ref
  const refs = {
    effectiveCourses: useRef<HTMLElement>(null),
    reviews: useRef<HTMLElement>(null),
    howWorks: useRef<HTMLElement>(null),
    scienceBehind: useRef<HTMLElement>(null),
    FAQ: useRef<HTMLElement>(null),
  };

  // 封装滚动方法
  const scrollToSection = useCallback((key: SectionKey) => {
    refs[key]?.current?.scrollIntoView({ behavior: 'smooth' });
  }, []);

  return { refs, scrollToSection };
};
  1. 父组件使用Hook
const Home: React.FC = () => {
  const { refs, scrollToSection } = useScrollToSection();
  
  return (
    <MainLayout>
      <main>
        <EffectiveCourses 
          ref={refs.effectiveCourses} 
          onNext={() => scrollToSection('reviews')} 
        />
        <Reviews 
          ref={refs.reviews} 
          onNext={() => scrollToSection('howWorks')} 
        />
        <HowSleepSchoolWork 
          ref={refs.howWorks} 
          onNext={() => scrollToSection('scienceBehind')} 
        />
        <ScienceBehind 
          ref={refs.scienceBehind} 
          onNext={() => scrollToSection('FAQ')} 
        />
        <FAQ ref={refs.FAQ} />
      </main>
    </MainLayout>
  );
};
  1. 子组件调整
import { forwardRef } from 'react';

// 子组件props类型,仅接收滚动触发方法
type ScienceBehindProps = {
  onNext: () => void;
};

// forwardRef泛型:第一个是子组件自身的ref类型,第二个是props类型
const ScienceBehind = forwardRef<HTMLElement, ScienceBehindProps>(({ onNext }, ref) => (
  <section className={`${styles.section}`} ref={ref}>
    <div className="container">
      <h2 className={styles.sectionTitle}>Science behind Sleep School</h2>
      {/* 按钮直接绑定onNext方法,无需接收外部ref */}
      <ArrowButton onClick={onNext} color="#082B4B" />
    </div>
  </section>
));

export default ScienceBehind;

这种方式下,每个子组件只需要关注自身的DOM ref和滚动触发逻辑,无需依赖外部的完整ref对象,类型更安全,代码维护性更强。


内容的提问来源于stack exchange,提问作者Ann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 04:00:03