我正在尝试创建一个函数,该函数包装具有 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
}
}
};
}
如果我像下面这样使用这个函数,我希望 context
的类型,因此 req
、res
和 params
的类型是已知的与 GetServerSideProps
s
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'
}
答案 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 允许您为一个函数声明多个不同的调用签名,并且单一实现必须适用于所有调用签名。编译器非常松散地检查这些实现,因此仍然有可能通过返回错误类型的值来欺骗编译器。但至少,您可以将可能的输出类型限制为仅 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'