TLDR: Typescript中是否有一种方法可以声明包含扩展给定接口的所有类型的类型?
我的具体问题
我正在编写一个自定义的React挂钩,该挂钩封装了用于确定是否将鼠标悬停在元素上的逻辑。它是在this hook之后大致建模的。它公开了一个应该可以使用任何HTMLElement的引用:
const ref = useRef<HTMLElement>(null);
问题是,如果我尝试在任何特定的React元素上使用此ref,则会收到一条错误消息,告诉我该特定元素不是HTMLElement
。例如,如果我将其与HTMLDivElement
一起使用,则会出现以下错误:argument of type HTMLElement is not assignable to parameter of type HTMLDivElement
。
这里是a simple repro case of the problem above in Typescript playground
很显然,我不想在我的钩子中列出所有html元素的类型。假设HTMLDivElement
扩展了HTMLElement
类型,是否有一种方法可以声明我实际上追求的类型不是严格的HTMLElement
,而是任何扩展了HTMLElement
的东西?>
反应代码示例
钩子的源代码
import { useRef, useState, useEffect } from 'react';
type UseHoverType = [React.RefObject<HTMLElement>, boolean];
export default function useHover(): UseHoverType {
const [isHovering, setIsHovering] = useState(false);
let isTouched = false;
const ref = useRef<HTMLElement>(null); // <-- What should the type be here?
const handleMouseEnter = () => {
if (!isTouched) {
setIsHovering(true);
}
isTouched = false;
};
const handleMouseLeave = () => {
setIsHovering(false);
};
const handleTouch = () => {
isTouched = true;
};
useEffect(() => {
const element = ref.current;
if (element) {
element.addEventListener('mouseenter', handleMouseEnter);
element.addEventListener('mouseleave', handleMouseLeave);
element.addEventListener('touchstart', handleTouch);
return () => {
element.removeEventListener('mouseenter', handleMouseEnter);
element.removeEventListener('mouseleave', handleMouseLeave);
element.removeEventListener('touchend', handleTouch);
};
}
}, [ref.current]);
return [ref, isHovering];
}
如果这样使用将产生类型错误:
import useHover from 'path-to-useHover';
const testFunction = () => {
const [hoverRef, isHovered] = useHover();
return (
<div
ref={hoverRef}
>
Stuff
</div>
);
}
上面示例中的类型错误将是:
Type 'RefObject<HTMLElement>' is not assignable to type 'string | RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined'.
Type 'RefObject<HTMLElement>' is not assignable to type 'RefObject<HTMLDivElement>'.
Property 'align' is missing in type 'HTMLElement' but required in type 'HTMLDivElement'.
答案 0 :(得分:1)
我认为您对失败的分配方向有误。如果您有接口A
,则与A
的所有子类匹配的类型称为A
。这样,HTMLElement
(即可以从分配)任何HTML元素,例如HTMLDivElement
。
这意味着,如果您有一堆函数,其中一个函数接受HTMLDivElement
,另一个函数接受HTMLLinkElement
等,则没有可以传递给所有函数的真实类型。这意味着您希望拥有一个既是div又是链接以及更多内容的元素。
根据您对问题的编辑进行编辑:
如果您拥有的代码可以正常工作,并且唯一的问题是它无法编译,则只需将useHover
泛型化,就像这样:
type UseHoverType<T extends HTMLElement> = [React.RefObject<T>, boolean];
function useHover<T extends HTMLElement>(): UseHoverType<T> {
const ref = useRef<T>(null); // <-- What should the type be here?
...
然后:
const testFunction = () => {
const [hoverRef, isHovered] = useHover<HTMLDivElement>();
这样的事情可以使您的代码正常编译,而无需更改其运行时行为。我无法确定当前的运行时行为是否符合期望。
答案 1 :(得分:0)
由于HTMLDivElement扩展了HTMLElement,因此可以正常工作。在您的typecirpt游乐场中,您将其混合了。我通过在此playground中切换x和y来更新它。您希望函数扩展HTMLElement并传递y(即HTMLDivElement)。那行得通。