要获得可用于创建函数的新类型,我们有以下步骤:
派生一个新的GetStaticPropsContext
类型,该类型不允许缺少locale
、locales
或defaultLocale
或undefined
.
派生一个使用我们的新上下文类型的新GetStaticProps
类型.
使用新props 类型,而不是GetStaticProps
.
第1部分和第2部分是可重用的,而第3部分(使用它)只是在函数上使用新类型.
请注意,这是为了获得最大的灵活性和最小的假设,因为您说过要避免在现有类型上进行复制和粘贴.通过做出更多的假设,可以让它变得不那么冗长.这就是说,实际的代码并不是很长(参见结尾),它只需要相当多的解释.
1. Derive a new GetStaticPropsContext
type
您可以创建更严格的Required
版本,该版本还从对象类型的属性中删除了undefined
:
type RequiredNotUndefined<T> = {
[Key in keyof T]-?: Exclude<T[Key], undefined>;
};
它使用带有mapping modifier的映射类型来删除可选性,使用Exclude
utility type来从其类型中删除undefined
.
然后,因为我们只想将它应用于GetStaticPropsContext
中的一些属性,所以我们可以使用此类型来拆分那些属性来传递上面的内容:
type SelectiveRequiredNotUndefined<T, Keys extends keyof T> =
RequiredNotUndefined<Pick<T, Keys>> & Omit<T, Keys>;
我们使用Pick
将RequiredNotUndefined
仅应用于Keys
中的命名属性,然后将这些属性与其他属性相交(通过Omit
).
然后,您可以创建从GetStaticPropsContext
派生的您自己的上下文类型,使您关心的三个属性成为必需的,而不是undefined
:
export type MyStaticContext = SelectiveRequiredNotUndefined<
GetStaticPropsContext,
"locale" | "locales" | "defaultLocale"
>;
到目前为止,这就是上下文部分;下面是它工作的一个例子:Playground link
2. Derive a new GetStaticProps
type
但是我们并不是真的想做type MyStaticContext = ___
;我们想要更新GetStaticProps
的context
参数类型.
我们可以通过映射函数类型并使用我们的SelectiveRequiredNotUndefined
类型修改context
的类型来实现这一点:
type UpdateContext<T> = T extends (context: infer Context extends GetStaticPropsContext) => infer Return
? (context: SelectiveRequiredNotUndefined<Context, "locale" | "locales" | "defaultLocale">) => Return
: never;
它使用功能强大但文档不足的infer
feature来让我们从GetStaticProps
获取上下文和返回类型;然后我们修改context
的类型.
然后我们用它来创造我们自己的MyGetStaticProps
:
type MyGetStaticProps = UpdateContext<GetStaticProps>;
3. Use the new props type instead of GetStaticProps
const getStaticProps: MyGetStaticProps = async (context) => {
// ...
};
全部加在一起
以下是所有这些(on the playground):
// Stand-in types
type ParsedUrlQuery = { x: number; };
type PreviewData = { data: Record<string, any>; };
type GetStaticPropsResult<X> = {blah: X; };
export type GetStaticPropsContext<
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
> = {
params?: Q;
preview?: boolean;
previewData?: D;
locale?: string;
locales?: string[];
defaultLocale?: string;
};
export type GetStaticProps<
P extends { [key: string]: any } = { [key: string]: any },
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
> = (
context: GetStaticPropsContext<Q, D>
) => Promise<GetStaticPropsResult<P>> | GetStaticPropsResult<P>
// ==== >>>One time<<< declarations that get reused
type RequiredNotUndefined<T> = {
[Key in keyof T]-?: Exclude<T[Key], undefined>;
};
type SelectiveRequiredNotUndefined<T, Keys extends keyof T> =
RequiredNotUndefined<Pick<T, Keys>> & Omit<T, Keys>;
type UpdateContext<T> = T extends (context: infer Context extends GetStaticPropsContext) => infer Return
? (context: SelectiveRequiredNotUndefined<Context, "locale" | "locales" | "defaultLocale">) => Return
: never;
type MyGetStaticProps = UpdateContext<GetStaticProps>;
// ^?
// ==== Example use:
const getStaticProps: MyGetStaticProps = async (context) => {
context.locale
// ^?
context.locales
// ^?
context.defaultLocale
// ^?
context.preview // As an example of a property that doesn't get modified
// ^?
return {blah: {}};
};