如何根据其他参数类型具有不同的回调签名?

时间:2019-01-22 14:58:06

标签: typescript

此刻我在玩打字稿,在掌握如何重载接受回调的函数时遇到困难。

我的功能是围绕canvas元素进行包装。它最多可以接受2个参数:

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

现在取决于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上存在的方法,反之亦然。

我们非常感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

这里的问题是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;
}