【问题标题】:Typescript: how to declare a type that includes all types extending a common type?Typescript:如何声明一个包含所有扩展通用类型的类型的类型?
【发布时间】:2019-12-03 16:30:45
【问题描述】:

TLDR:Typescript 中有没有一种方法可以声明一个类型,该类型包含扩展给定接口的所有类型?

我的具体问题

我正在编写一个自定义的 React 钩子,它封装了决定一个元素是否被鼠标悬停的逻辑。它大致仿照this hook。它公开了一个应该能够接受任何 HTMLElement 的 ref:

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'.

【问题讨论】:

  • “在任何特定的 React 元素上使用这个 ref”到底是什么意思?哪些代码不能为你编译?
  • @TPReal 我添加了一个中断的代码示例。

标签: reactjs typescript


【解决方案1】:

它按预期工作,因为 HTMLDivElement 扩展了 HTMLElement。在你的打字机操场上,你把它弄混了。我通过在playground 中切换 x 和 y 来更新它。您希望该函数扩展 HTMLElement 并将 y 和 HTMLDivElement 传递给它。这行得通。

【讨论】:

  • 是的,你是对的。麻烦的是,我原来的操场更好地反映了这个问题。我无法更改 React 的 div 元素构造函数返回的类型——它始终是 HTMLDivElement。同样,span 将返回 HTMLSpanElement,依此类推。我可以定义的唯一类型是 useRef 函数的类型(请参阅github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/…),我试图想出一个包含所有扩展 HTMLElement 的类型的类型。
  • 你可以只使用 useRef,但这很糟糕。你也可以像这样破解它: ref={inputEl as any as ((instance: HTMLInputElement | null) => void)}
【解决方案2】:

我认为您对失败的分配方向有误。如果你有一个接口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>();

这样的事情会让你的代码编译得很好,而不会改变它的运行时行为。我无法判断现在的运行时行为是否符合预期。

【讨论】:

  • 就我而言,我希望有一个元素实现addEventListenerremoveEventListener 方法。它可以是 div、span 或链接等。所以当我创建 React 的 ref 时,我会说“这里有一个可以添加或删除事件侦听器的任何东西的 ref”。当我将此引用传递给 React 的 div 元素时,我希望它能够工作。但是现在我想多了,我相信你一定是对的,如果 React 的 div 元素返回 HTMLDivElement 类型,我无法说服 React 的 MutableRefObject 接受更通用的类型。
  • 是的!非常感谢;我不经常使用泛型并且一直忘记它们的力量。
猜你喜欢
  • 2021-05-03
  • 2020-05-04
  • 2017-02-12
  • 1970-01-01
  • 1970-01-01
  • 2020-05-15
  • 2018-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多