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泛型参数,解决类型不匹配问题:
- 定义统一的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>; };
- 父组件中指定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> ); };
- 子组件中指定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对象,只需接收滚动触发方法,代码更清晰、耦合度更低:
- 自定义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 }; };
- 父组件使用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> ); };
- 子组件调整
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
相关产品推荐
相关产品推荐

