如何在TypeScript中将forwardRef的ref传入自定义Hook?
解决useIntersection Hook与forwardRef配合的TypeScript类型问题
我实现了一个useIntersection Hook用于监听元素交叉状态,但在和forwardRef配合使用时遇到TypeScript类型错误。
原Hook代码:
import { MutableRefObject, useEffect } from 'react'; export default function useIntersection<T extends HTMLElement, U extends HTMLElement>(ref: MutableRefObject<T>, nodeCallback: () => U) { useEffect(() => { function callback([entry]) { ref.current.style.opacity = entry.isIntersecting ? '0' : ''; } const node = nodeCallback(); const observer = new IntersectionObserver(callback, { root: node }); observer.observe(ref.current); return () => observer.unobserve(ref.current); }, [ref.current]); }
配合forwardRef使用的组件代码:
import { forwardRef } from 'react'; const DownArrow = require('@site/static/img/down-arrow.svg').default; import styles from './index.module.css'; import useIntersection from '@site/src/hooks/useIntersection'; export default forwardRef<HTMLDivElement, {}>(function MoreIcon({}, ref): JSX.Element { useIntersection(ref, () => document.querySelector('footer')); return ( <div className={styles.arrow} ref={ref}> <DownArrow /> </div> ); });
遇到的错误:
Argument of type 'ForwardedRef' is not assignable to parameter of type 'MutableRefObject'.
Type '(instance: HTMLDivElement) => void' is not assignable to type 'MutableRefObject'.
核心问题
forwardRef传递的ref参数类型是ForwardedRef<T>,它包含两种可能的类型:
MutableRefObject<T | null>:对象类型的ref(instance: T | null) => void:回调类型的ref
而原Hook只接受MutableRefObject<T>,自然会出现类型不兼容的错误。
修改后的Hook代码
我们需要让Hook兼容两种ref类型,同时处理元素可能为null的边界情况:
import { Ref, useEffect, useRef } from 'react'; export default function useIntersection<T extends HTMLElement, U extends HTMLElement>( ref: Ref<T>, nodeCallback: () => U | null ) { // 内部ref统一存储目标元素,适配两种ref类型 const targetRef = useRef<T | null>(null); useEffect(() => { // 处理传入的ref:如果是回调类型,绑定到内部ref;如果是对象类型,直接取current if (typeof ref === 'function') { ref((element) => { targetRef.current = element; }); } else { targetRef.current = ref?.current ?? null; } const targetElement = targetRef.current; if (!targetElement) return; const rootNode = nodeCallback(); const observer = new IntersectionObserver(([entry]) => { targetElement.style.opacity = entry.isIntersecting ? '0' : ''; }, { root: rootNode }); observer.observe(targetElement); // 清理函数:取消监听,若为回调ref则清空 return () => { observer.unobserve(targetElement); if (typeof ref === 'function') { ref(null); } }; }, [ref, nodeCallback]); }
修改后的组件使用
组件中只需确保nodeCallback的返回值类型正确(处理document.querySelector可能返回null的情况):
import { forwardRef } from 'react'; const DownArrow = require('@site/static/img/down-arrow.svg').default; import styles from './index.module.css'; import useIntersection from '@site/src/hooks/useIntersection'; export default forwardRef<HTMLDivElement, {}>(function MoreIcon({}, ref) { useIntersection(ref, () => document.querySelector('footer') as HTMLElement | null); return ( <div className={styles.arrow} ref={ref}> <DownArrow /> </div> ); });
关键优化点
- 类型兼容:改用
Ref<T>作为参数类型,覆盖对象ref和回调ref两种场景 - 边界处理:判断目标元素是否存在,避免调用
observe时传入null - 依赖优化:useEffect依赖数组使用
ref而非ref.current,因为ref本身是稳定引用 - 内存清理:在卸载时取消监听,同时清理回调ref的引用
内容的提问来源于stack exchange,提问作者jcubic
相关产品推荐
相关产品推荐

