首页 > 解决方案 > 如何根据其他参数类型有不同的回调签名?

问题描述

我目前正在使用打字稿并且难以掌握如何重载函数,即接受回调。

我的函数是一个画布元素的包装器。它应该接受最多 2 个参数:

  1. 接受渲染上下文的回调
  2. 一个可选对象,您可以在其中定义要返回的上下文。它有一个type和一个attributes属性,基于画布的getContext方法。

现在根据type属性的值,我希望回调签名不同。如果值为 '2d' 或省略,则应为(context: CanvasRenderingContext2D) => void,如果值为 'webgl2',则应为(context: WebGL2RenderingContext) => void,依此类推。

现在的问题是,我不想(context: any) => void在实际实现中作为我的回调签名(我的 linter 禁止这样做)。我需要如何定义这个签名,以获得我想要的结果?

您可以在下面看到我的尝试,但它给了我错误:

重载签名与函数实现不兼容。

function useCanvas(
  draw: (context: WebGLRenderingContext) => void,
  options: { type: 'webgl' | 'experimental-webgl'; attributes?: WebGLContextAttributes },
): React.RefObject<HTMLCanvasElement>;
function useCanvas(
  draw: (context: WebGL2RenderingContext) => void,
  options: { type: 'webgl2'; attributes?: WebGLContextAttributes },
): React.RefObject<HTMLCanvasElement>;
function useCanvas(
  draw: (context: CanvasRenderingContext2D) => void,
  options?: { type?: '2d'; attributes?: CanvasRenderingContext2DSettings },
): React.RefObject<HTMLCanvasElement>;
function useCanvas(
  draw: (
    context: CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext,
  ) => void,
  {
    type = '2d',
    attributes,
  }: {
    type?: '2d' | 'webgl' | 'experimental-webgl' | 'webgl2';
    attributes?: CanvasRenderingContext2DSettings | WebGLContextAttributes;
  } = {},
): React.RefObject<HTMLCanvasElement> {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    if (canvasRef.current !== null) {
      ctx = canvasRef.current.getContext(type, attributes);

      draw(ctx);
    }
  });

  return canvasRef;
}

这个想法是,打字稿可以在你编写它时干扰回调参数是什么。那么就不可能在 2D 上下文中使用仅存在于 WebGL 上的方法,反之亦然。

非常感谢任何帮助!

标签: typescript

解决方案


这里的问题是 TypeScript 没有将函数的联合统一为一个参数的函数,该参数是一个联合。

getContext标准库中的定义不区分不同类型的另一个问题,我们可以通过扩充该接口来解决这个问题:

interface HTMLCanvasElement {
  getContext(
    contextId: '2d', attributes?: CanvasRenderingContext2DSettings
  ): CanvasRenderingContext2D | null
  getContext(
    contextId: 'webgl' | 'experimental-webgl', attributes?: WebGLContextAttributes
  ): WebGLRenderingContext | null
  getContext(
    contextId: 'webgl2', attributes?: WebGLContextAttributes
  ): WebGL2RenderingContext | null
}

现在,问题是我们需要将传入的参数与正确的属性和回调相匹配。您可以使用元组的联合作为参数的类型,但要使其工作,您首先需要始终通过索引而不是解构来引用单个参数(TS 人员正在讨论修复此问题),其次您必须有鉴别器(即类型)在顶层:

function useCanvas(
  ...args:
    [
      '2d',
      (context: CanvasRenderingContext2D) => void,
      CanvasRenderingContext2DSettings?
    ] |
    [
      'webgl' | 'experimental-webgl',
      (context: WebGLRenderingContext) => void,
      WebGLContextAttributes?
    ] |
    [
      'webgl2',
      (context: WebGL2RenderingContext) => void,
      WebGLContextAttributes?
    ]
): React.RefObject<HTMLCanvasElement> {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    if (canvasRef.current !== null) {
      switch (args[0]) {
        case 'webgl': // fall-through
        case 'experimental-webgl': {
          const type = args[0];
          const ctx = canvasRef.current.getContext(type, args[2]);
          ctx && args[1](ctx);
          break;
        }
        case 'webgl2': {
          const ctx = canvasRef.current.getContext(args[0], args[2]);
          ctx && args[1](ctx);
          break;
        }
        default: {
          const ctx = canvasRef.current.getContext('2d', args[2]);
          ctx && args[1](ctx);
          break;
        }
      }
    }
  });

  return canvasRef;
}

推荐阅读