首页 > 解决方案 > 将受限泛型类型作为参数和返回类型的函数

问题描述

我正在尝试创建一个函数,该函数包装具有任一类型的函数GetStaticPropsGetServerSideProps返回具有相同类型的函数,该函数包装作为参数传递的函数(同样,相同类型)。

这样包装器就可以准确地知道包装了什么,我相信我可以用泛型来做到这一点。

如何修复以下示例?预期的结果是我只能传递 GetStaticProps 或 GetServerSideProps 类型的函数,并且无论在何处使用此函数,TypeScript(因此我的 IDE)都会知道我传递了什么。

export type GetGenericProps = GetStaticProps | GetServerSideProps;

export function handleGetPagePropsErrors<T extends GetGenericProps>(
  wrappedHandler: T,
): T {
  return async (context) => {
    try {
      return await wrappedHandler(context);
    } catch (err) {
      if (err instanceof AppError) {
        return {
          props: {
            error: {
              message: err.message,
              type: err.type,
            }
          }
        };
      } else {
        throw err; // just let Next.js handle it
      }
    }
  };
}

如果我像下面这样使用这个函数,我希望 的类型context,因此 , 和 的req类型res与sparams相同GetServerSideProps

export const getServerSideProps: GetServerSideProps = handleGetPagePropsErrors(
  async ({ req, res, params }) => {
    if (Math.random() > 0.5) {
      throw new AppError(
        ErrorType.BAD_THINGS_HAPPEN, 
        "Sometimes code just doesn't work, dude"
      );
    }

    return {
      props: {
        foo: 'bar'
      },
    };
  },
);

AppError只是一个简单的类扩展Error,包括错误类型(来自枚举)

class AppError extends Error {
  type: ErrorType;

  constructor(type: ErrorType, message: string) {
    super(message);
    this.type = type;
  }
}

enum ErrorType {
  BAD_THINGS_HAPPEN = 'BAD_THINGS_HAPPEN'
}

标签: typescriptnext.js

解决方案


如果您确定某个值属于特定类型,但编译器无法验证这一点并抱怨它,您可以使用类型断言来消除其警告。(有时你断言的类型与编译器期望你必须做一个中间类型断言的类型无关。所以如果foo as Bar不起作用,你总是可以写foo as any as Bar。)

对于您的代码,这将意味着以下内容:

function handleGetPagePropsErrors<T extends GetGenericProps>(
  wrappedHandler: T,
): T {
  return (async (context: any) => {
    try {
      return await wrappedHandler(context);
    } catch (err) {
      if (err instanceof AppError) {
        return {
          props: {
            message: err.message,
            type: err.type,
          },
        };
      } else {
        throw err; // just let Next.js handle it
      }
    }
  }) as T; // <-- assert here
}

请注意,通过这样做,您将在这种情况下从编译器中承担类型安全的责任。如果你的类型断言结果不正确,那么你对编译器撒了谎,直到遇到一些运行时问题,你才会发现这一点。所以要小心。

有了上述内容,泛型类型T extends GetGenericProps可以比联合元素中的一个或另一个更具体。例如,typeScript 允许您在 functions 上设置“expando”属性,如下所示:

const gssp = async ({ req, res, params }: GetServerSidePropsContext) => {
  if (Math.random() > 0.5) {
    throw new AppError(
      ErrorType.BAD_THINGS_HAPPEN,
      "Sometimes code just doesn't work, dude"
    );
  }

  return {
    props: {
      foo: 'bar'
    },
  };
};
gssp.expandoProp = "oops";

gsspa也是如此GetServerSideProps,但它也有一个string-valuedexpandoProp属性。类似的东西GetServerSideProps & {expandoProp: string}。这意味着编译器会认为 的输出handleGetPagePropsErrors(gssp)将具有这样的属性:

const getServerSideProps = handleGetPagePropsErrors(gssp);
getServerSideProps.expandoProp.toUpperCase(); // okay?!
// no error from compiler, likely error at runtime

但当然不会,因为您的实现handleGetPagePropsErrors()不会返回与输入完全相同类型的值,而是相关类型的值。从技术上讲,as T这是一个谎言。

在实践中,您很可能不会遇到这种奇怪的边缘情况。我只是想让你知道存在这样的问题,并且在使用类型断言时要小心。


这里的另一种可能性是做一些类型更容易保证的事情(但仍然将一些类型安全负担从编译器转移到你自己身上),并将其编写hangleGetPagePropsErrors()重载函数

TypeScript 允许您为一个函数声明多个不同的调用签名,一个实现必须适用于所有调用签名。编译器非常松散地检查这些实现,因此仍然有可能通过返回错误类型的值来欺骗编译器。不过,至少,您可以将可能的输出类型限制为只是GetStaticPropsGetServerSideProps不是每个可能的通用子类型GetGenericProps

以下是你的做法:

function handleGetPagePropsErrors(wrappedHandler: GetStaticProps): GetStaticProps;
function handleGetPagePropsErrors(wrappedHandler: GetServerSideProps): GetServerSideProps;
function handleGetPagePropsErrors(wrappedHandler: GetGenericProps): GetGenericProps {
  return (async (context: any) => {
    try {
      return await wrappedHandler(context);
    } catch (err) {
      if (err instanceof AppError) {
        return {
          props: {
            message: err.message,
            type: err.type,
          },
        };
      } else {
        throw err; // just let Next.js handle it
      }
    }
  });
}

你可以看到之前的expando属性问题已经不存在了;该函数并不打算返回比以下更精确的东西GetServerSideProps

const getServerSideProps = handleGetPagePropsErrors(gssp);
// getServerSideProps: GetServerSideProps
getServerSideProps.expandoProp.toUpperCase(); // error!
// Property 'expandoProp' does not exist on type 'GetServerSideProps'

Playground 代码链接


推荐阅读