Skip to main content

针对模型类型的部分结构进行操作

使用 Prisma 客户端时,Prisma 架构 中的每个模型都会转换为专用的 TypeScript 类型。例如,假设你有以下 UserPost 型号:

¥When using Prisma Client, every model from your Prisma schema is translated into a dedicated TypeScript type. For example, assume you have the following User and Post models:

model User {
id Int @id
email String @unique
name String?
posts Post[]
}

model Post {
id Int @id
author User @relation(fields: [userId], references: [id])
title String
published Boolean @default(false)
userId Int
}

从该模式生成的 Prisma 客户端代码包含 User 类型的表示形式:

¥The Prisma Client code that's generated from this schema contains this representation of the User type:

export type User = {
id: string
email: string
name: string | null
}

问题:使用生成的模型类型的变体

¥Problem: Using variations of the generated model type

描述

¥Description

在某些情况下,你可能需要生成的 User 类型的变体。例如,当你有一个函数需要带有 posts 关系的 User 模型实例时。或者,当你需要一种类型在应用代码中仅传递 User 模型的 emailname 字段时。

¥In some scenarios, you may need a variation of the generated User type. For example, when you have a function that expects an instance of the User model that carries the posts relation. Or when you need a type to pass only the User model's email and name fields around in your application code.

解决方案

¥Solution

作为解决方案,你可以使用 Prisma 客户端的辅助程序类型自定义生成的模型类型。

¥As a solution, you can customize the generated model type using Prisma Client's helper types.

User 类型仅包含模型的 scalar 字段,但不考虑任何关系。那是因为 Prisma 客户端查询中的 默认情况下不包括关系

¥The User type only contains the model's scalar fields, but doesn't account for any relations. That's because relations are not included by default in Prisma Client queries.

但是,有时拥有包含关系的可用类型(即从使用 include 的 API 调用中获取的类型)很有用。同样,另一个有用的场景可能是拥有一个仅包含模型标量字段的子集的可用类型(即从使用 select 的 API 调用中获取的类型)。

¥However, sometimes it's useful to have a type available that includes a relation (i.e. a type that you'd get from an API call that uses include). Similarly, another useful scenario could be to have a type available that includes only a subset of the model's scalar fields (i.e. a type that you'd get from an API call that uses select).

实现此目的的一种方法是在应用代码中手动定义这些类型:

¥One way of achieving this would be to define these types manually in your application code:

// 1: Define a type that includes the relation to `Post`
type UserWithPosts = {
id: string
email: string
name: string | null
posts: Post[]
}

// 2: Define a type that only contains a subset of the scalar fields
type UserPersonalData = {
email: string
name: string | null
}

虽然这当然是可行的,但这种方法增加了 Prisma 模式更改时的维护负担,因为你需要手动维护类型。对此的一个更清晰的解决方案是结合 validator 使用由 Prisma Client 在 Prisma 命名空间下生成和公开的 UserGetPayload 类型。

¥While this is certainly feasible, this approach increases the maintenance burden upon changes to the Prisma schema as you need to manually maintain the types. A cleaner solution to this is to use the UserGetPayload type that is generated and exposed by Prisma Client under the Prisma namespace in combination with the validator.

以下示例使用 Prisma.validator 创建两个类型安全对象,然后使用 Prisma.UserGetPayload 实用函数创建一个可用于返回所有用户及其帖子的类型。

¥The following example uses the Prisma.validator to create two type-safe objects and then uses the Prisma.UserGetPayload utility function to create a type that can be used to return all users and their posts.

import { Prisma } from '@prisma/client'

// 1: Define a type that includes the relation to `Post`
const userWithPosts = Prisma.validator<Prisma.UserDefaultArgs>()({
include: { posts: true },
})

// 2: Define a type that only contains a subset of the scalar fields
const userPersonalData = Prisma.validator<Prisma.UserDefaultArgs>()({
select: { email: true, name: true },
})

// 3: This type will include a user and all their posts
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>

后一种方法的主要好处是:

¥The main benefits of the latter approach are:

  • 更简洁的方法,因为它利用 Prisma Client 的生成类型

    ¥Cleaner approach as it leverages Prisma Client's generated types

  • 模式更改时减少维护负担并提高类型安全性

    ¥Reduced maintenance burden and improved type safety when the schema changes

问题:访问函数的返回类型

¥Problem: Getting access to the return type of a function

描述

¥Description

当对模型执行 selectinclude 操作并从函数返回这些变体时,可能很难访问返回类型,例如:

¥When doing select or include operations on your models and returning these variants from a function, it can be difficult to gain access to the return type, e.g:

// Function definition that returns a partial structure
async function getUsersWithPosts() {
const users = await prisma.user.findMany({ include: { posts: true } })
return users
}

从上面的代码片段中提取代表 "有帖子的用户" 的类型需要一些高级的 TypeScript 使用:

¥Extracting the type that represents "users with posts" from the above code snippet requires some advanced TypeScript usage:

// Function definition that returns a partial structure
async function getUsersWithPosts() {
const users = await prisma.user.findMany({ include: { posts: true } })
return users
}

// Extract `UsersWithPosts` type with
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T
type UsersWithPosts = ThenArg<ReturnType<typeof getUsersWithPosts>>

// run inside `async` function
const usersWithPosts: UsersWithPosts = await getUsersWithPosts()

解决方案

¥Solution

你可以使用原生的 TypeScript 工具类型 AwaitedReturnType 来优雅地解决这个问题:

¥You can use native the TypeScript utility type Awaited and ReturnType to solve the problem elegantly:

type UsersWithPosts = Awaited<ReturnType<typeof getUsersWithPosts>>