将受限泛型类型作为参数和返回类型的函数

时间:2021-04-20 17:26:25

标签: typescript next.js

我正在尝试创建一个函数,该函数包装具有 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 的类型,因此 reqresparams 的类型是已知的与 GetServerSidePropss

相同
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'
}

1 个答案:

答案 0 :(得分:1)

如果您确定某个值属于特定类型,但编译器无法验证这一点并对此进行抱怨,您可以使用 type assertion 来消除其警告。 (有时您断言的类型与编译器期望的类型无关,您必须进行中间类型断言。因此,如果 foo as Bar 不起作用,您始终可以编写 foo as any as Bar。)< /p>

对于您的代码,这意味着:

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 让你set "expando" properties on functions,像这样:

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";

所以 gssp 是一个 GetServerSideProps,但它也有一个 string 值的 expandoProp 属性。类似 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() 写成 {{3} }.

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'

overloaded function

相关问题