我正在开发一个Next.js应用程序,该类型用于在页面中定义getStaticProps个方法:

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>

问题是,GetStaticPropsContext的定义如下,并且不是通用的:

export type GetStaticPropsContext<
  Q extends ParsedUrlQuery = ParsedUrlQuery,
  D extends PreviewData = PreviewData
> = {
  params?: Q
  preview?: boolean
  previewData?: D
  locale?: string
  locales?: string[]
  defaultLocale?: string
}

在我的应用程序中,localelocalesdefaultLocale永远不是undefined

有没有一种方法可以覆盖GetStaticProps上的GetStaticPropsContext,而无需将GetStaticProps完全复制/粘贴到新类型,并用另一种类型更改GetStaticPropsContext

如果我必须复制所有东西,我只是担心可维护性,并想知道是否有更好的方法.

推荐答案

要获得可用于创建函数的新类型,我们有以下步骤:

  1. 派生一个新的GetStaticPropsContext类型,该类型不允许缺少localelocalesdefaultLocaleundefined.

  2. 派生一个使用我们的新上下文类型的新GetStaticProps类型.

  3. 使用新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>;

我们使用PickRequiredNotUndefined仅应用于Keys中的命名属性,然后将这些属性与其他属性相交(通过Omit).

然后,您可以创建从GetStaticPropsContext派生的您自己的上下文类型,使您关心的三个属性成为必需的,而不是undefined:

export type MyStaticContext = SelectiveRequiredNotUndefined<
    GetStaticPropsContext,
    "locale" | "locales" | "defaultLocale"
>;

到目前为止,这就是上下文部分;下面是它工作的一个例子:Playground link

2. Derive a new GetStaticProps type

但是我们并不是真的想做type MyStaticContext = ___;我们想要更新GetStaticPropscontext参数类型.

我们可以通过映射函数类型并使用我们的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: {}};
};

Typescript相关问答推荐

以Angular 自定义快捷菜单

Vue3 Uncaught SyntaxError:每当我重新加载页面时,浏览器控制台中会出现无效或意外的令牌

如何过滤文字中的类对象联合类型?

我在Angular 17中的environment.ts文件中得到了一个属性端点错误

专用路由组件不能从挂钩推断类型

类型脚本-处理值或返回值的函数

可赋值给类型的类型,从不赋值

在类型脚本中创建显式不安全的私有类成员访问函数

推断集合访问器类型

仅当类型为联合类型时映射属性类型

有没有一种方法可以提升对象的泛型级别,而对象的值是泛型函数?

可以将JS文件放在tsconfig';s includes/files属性?或者,我如何让tsc判断TS项目中的JS文件?

Typescript泛型验证指定了keyof的所有键

TypeScript是否不知道其他函数内部的类型判断?

如何通过nx找到所有受影响的项目?

为什么 TypeScript 类型条件会影响其分支的结果?

如何在 TypeScript 类型定义中简化定义长联合类型

如何为字符串模板文字创建类型保护

将函数签名合并到重载中

如何编写一个接受 (string|number|null|undefined) 但只返回 string 且可能返回 null|undefined 的函数?