Typescript 函数,该函数将受限泛型类型作为参数和返回类型

Typescript 函数,该函数将受限泛型类型作为参数和返回类型,typescript,next.js,Typescript,Next.js,我试图创建一个函数,该函数使用类型GetStaticProps或GetServerSideProps包装函数,并返回一个相同类型的函数,该函数包装作为参数传递的函数(同样,相同类型) 这是为了让包装器确切地知道包装的内容,我相信我可以用泛型实现这一点 如何修复以下示例?预期的结果是,我只能传递GetStaticProps或GetServerSideProps类型的函数,无论在何处使用该函数,TypeScript(以及我的IDE)都会知道我传递了什么 export type GetGenericP

我试图创建一个函数,该函数使用类型
GetStaticProps
GetServerSideProps
包装函数,并返回一个相同类型的函数,该函数包装作为参数传递的函数(同样,相同类型)

这是为了让包装器确切地知道包装的内容,我相信我可以用泛型实现这一点

如何修复以下示例?预期的结果是,我只能传递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
      }
    }
  };
}
AppError
只是一个扩展
Error
的简单类,其中包含错误类型(来自枚举)


如果您确定某个值属于特定类型,但编译器无法验证该值并对此进行投诉,则可以使用来消除其警告。(有时您正在断言的类型与编译器希望您执行的类型无关。因此,如果
foo as Bar
不起作用,您可以始终将
foo编写为任意的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
}
因此,
gssp
是一个
GetServerSideProps
,但它也有一个
string
值的
expandoProp
属性。类似于
GetServerSideProps&{expandoProp:string}
。这意味着编译器将认为
HandleGetPageProperErrors(gssp)
的输出也将具有这样的属性:

const getServerSideProps = handleGetPagePropsErrors(gssp);
getServerSideProps.expandoProp.toUpperCase(); // okay?!
// no error from compiler, likely error at runtime
但当然不会,因为
handleGetPagePropErrors()
的实现不会返回与输入类型完全相同的值,而是返回相关类型的值。从技术上讲,
as T
是一个谎言

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


这里的另一种可能性是做一些类型更容易保证的事情(但仍然会从编译器和您自己身上卸下一些类型安全负担),并将
hanglegetpagepropersErrors()
作为一个例子编写

TypeScript允许您为一个函数声明多个不同的调用签名,并且只有一个实现必须适用于所有调用签名。编译器非常松散地检查这些实现,因此仍然可能通过返回错误类型的值来欺骗编译器。但是,至少可以将可能的输出类型限制为
GetStaticProps
GetServerSideProps
,而不是
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'


这似乎不是一个好消息,因为
wrappedHandler
的返回类型可能不包括
{props:{message:…}
,因此
HandleGetPagePropertors(wrappedHandler)
不会返回
t
。您可以忽略这一点,使用类型断言(如)或重构来实现类型安全,但更复杂。我很乐意将此作为一个答案来写,但是您是否可以在这里编辑代码,并使用一些实际调用
HandleGetPagePropertErrors()
的用例?我想知道它的结果是您所期望的类型。事实上,按照您所说的进行类型断言可以得到预期的结果。如果返回函数的类型是从
T
知道的,那就太好了,但对我来说已经足够了。我已经用可能感兴趣的更多信息更新了这个问题。我不知道如何提供更多的用例,我唯一可以做的其他用例是切换到
GetStaticProps
。在这种情况下,包装函数中的
上下文的类型应推断为
GetStaticPropsContext
,而不是
GetServerSidePropsContext
。不管怎样,正如我之前所说,做类型断言对我来说非常有效。谢谢,我想我会写一个答案。您可能还需要,因为它没有泛型所具有的一些奇怪问题。噢,像Java!我不知道Typescript有这个功能,那会派上用场的,非常感谢!如果你最后写了一个答案,我会很高兴地把它标为最好的。
const getServerSideProps = handleGetPagePropsErrors(gssp);
getServerSideProps.expandoProp.toUpperCase(); // okay?!
// no error from compiler, likely error at runtime
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
      }
    }
  });
}
const getServerSideProps = handleGetPagePropsErrors(gssp);
// getServerSideProps: GetServerSideProps
getServerSideProps.expandoProp.toUpperCase(); // error!
// Property 'expandoProp' does not exist on type 'GetServerSideProps'