Refine 与 TypeScript Omit 类型:掌握对象类型裁剪,写出更精简的代码
2026/9/10 22:05:20 网站建设 项目流程

Refine 与 TypeScript Omit 类型:掌握对象类型裁剪,写出更精简的代码

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

导读

Omit<Type, Keys>是 TypeScript 内置的实用类型之一,它允许你从既有类型中移除指定的属性,从而派生出新的类型。本文以本仓库(refine,一个用于构建内部工具、后台面板与 B2B 应用的 React 框架)的官方技术博客为蓝本,通过SubscriberPublicUserFrontendUser等完整示例讲解Omit<>的用法,并深入 refine 各包源码,展示Omit<>在真实框架代码中如何被用来裁剪 react-query 选项、Pagination 与按钮组件 props。读完本文,你将掌握Omit<>的语法、与Pick<>的取舍、编译期与运行时的差异,以及如何在日常开发与开源项目维护中用它精简类型。

什么是 TypeScriptOmit<Type, Keys>

TL;DR:Omit<Type, Keys>通过从既有类型中删除某些属性来构造一个新类型。当某些属性不需要时,它是减少冗余代码的绝佳工具。

Pick<>类似,Omit<>接受两个参数:

  • 第一个参数是基础类型(Type);
  • 第二个参数是要省略的属性键的联合类型Keys,多个键用|分隔);
  • 返回值是排除了这些属性后的派生类型。

本篇文章属于《TypeScript Utility Type Series》系列的第二篇,前一篇《A Guide on TypeScript Pick Type》见 documentation/blog/2024-12-17-typescript-pick.md。上一篇中我们用Pick<>从基础类型SuperbUser中挑选少量属性派生出GuestUser,并提示:当需要保留的属性数量多于需要丢弃的属性数量时,Pick<>反而不太方便,此时应优先考虑Omit<>。本篇我们就围绕这一点,用Omit<>Subscriber(订阅者)实体创建类型。

TypeScriptOmit<Type, Keys>实战示例

假设我们有一张用户关系表,其中SuperbUser实体拥有以下属性:

type SuperbUser = { userId: number; macAddress: string; username: string; email: string; password: string; firstName: string; lastName: string; roles: ("Admin" | "Editor" | "Author")[]; };

Subscriber实体拥有与SuperbUser完全相同的属性——唯独没有roles。如果使用Pick<>,我们需要把除了roles之外的所有属性逐一列出;而使用Omit<>只需要指定要删除的那一个键:

type Subscriber = Omit<SuperbUser, "roles">;

这种情况下,Omit<>Pick<>更方便。现在我们可以创建一个不再包含roles字段的订阅者对象:

const subscriber: Subscriber = { userId: 4, macAddress: "a:5ub:mach1ne", username: "sub_user", email: "sub_user@gmail.com", password: "12345678", firstName: "Abdullah", lastName: "Numan", }; console.log(subscriber); /* { "userId": 4, "macAddress": "a:5ub:mach1ne", "username": "sub_user", "email": "sub_user@gmail.com", "password": "12345678", "firstName": "Abdullah", "lastName": "Numan" } */ console.log(subscriber.roles); // undefined

注意:subscriber.roles在类型层面已被移除,访问它返回undefined。如果我们强行在开发期给它赋值:

subscriber.roles = ["Reader", "Commenter"]; // Property 'roles' does not exist on type 'Subscriber'.

TypeScript 会立刻报错:

// Property 'roles' does not exist on type 'Subscriber'.

但有趣的是,如果我们此时再看console.log(subscriber.roles),会发现这个赋值实际上在运行时真的生效了。也就是说,Omit<>只是开发期的类型约束,TypeScript 并不会在编译成 JavaScript 之后继续检查代码的任何后果。JavaScript 引擎会照常把subscriber.roles的值设置进去。

重要提醒:Omit<>是开发期(编译期)的工具,不是运行时防护。务必结合 linter(如@typescript-eslint)与类型检查来严格执行 omit 约束,防止敏感字段意外暴露。

TypeScriptOmit<>与 Interface

Pick<>一样,基础类型也可以是interface,结果完全相同:

interface SuperbUser { userId: number; macAddress: string; username: string; email: string; password: string; firstName: string; lastName: string; roles: ("Admin" | "Editor" | "Author")[]; } type Subscriber = Omit<SuperbUser, "roles">; const subscriber: Subscriber = { userId: 4, macAddress: "a:5ub:mach1ne", username: "sub_user", email: "sub_user@gmail.com", password: "12345678", firstName: "Abdullah", lastName: "Numan", }; console.log(subscriber); /* { "userId": 4, "macAddress": "a:5ub:mach1ne", "username": "sub_user", "email": "sub_user@gmail.com", "password": "12345678", "firstName": "Abdullah", "lastName": "Numan" } */ console.log(subscriber.roles); // undefined

无论基础类型是type还是interfaceOmit<>的第二个参数都接受属性键的联合类型,多个键用管道符|连接:

type Subscriber = Omit<SuperbUser, 'roles' | 'firstName' | ...>;

何时应该避免使用Omit<>

Pick<>相对的取舍规则同样成立:当需要省略的属性多于需要保留的属性时,应避免使用Omit<>,转而使用Pick<>

例如:如果SuperbUser有 8 个属性,而你只需要其中的 2 个,那么Omit<>需要列出 6 个要删除的键,远不如Pick<>列出 2 个要保留的键来得直观、不易出错。选择标准很简单——保留的少用Pick<>,丢弃的少用Omit<>

何时应该使用 TypeScriptOmit<>

Omit<>的核心价值在于:保留大部分字段、只剔除少数几个。典型场景包括:

  • 移除敏感字段:例如从用户对象中剔除passwordpasswordHashcreatedAt等;
  • 为复杂类型生成简化版本:基础类型很庞大,但某些场景只需要其中绝大部分字段;
  • 快速适配第三方类型:不想手写一个全新的类型,只想对现有类型做微调。

下面逐一展开。

简化派生类型

当你有一个复杂的基础类型,但需要一个去掉少数字段的简化版本时,Omit<>非常方便:

type FullUser = { id: number; name: string; email: string; password: string; createdAt: Date; }; // 创建一个不含敏感数据(password、createdAt)的 PublicUser 类型 type PublicUser = Omit<FullUser, "password" | "createdAt">; const user: PublicUser = { id: 1, name: "John Doe", email: "johndoe@gmail.com", }; console.log(user); /* Output: { id: 1, name: "John Doe", email: "johndoe@gmail.com" } */

API 数据过滤

有时候 API 或后端会返回完整的对象,而前端只需要其中一小部分字段。你可以手写一个全新的类型,也可以直接用Omit<>快速裁剪:

interface ApiResponse { id: number; username: string; email: string; passwordHash: string; isAdmin: boolean; } // 创建一个不含敏感后端数据的 FrontendUser 类型 type FrontendUser = Omit<ApiResponse, "passwordHash" | "isAdmin">; const frontendUser: FrontendUser = { id: 101, username: "frontend_dev", email: "dev@example.com", };

为特定上下文创建更干净的类型

当你在处理表单、UI 组件或其他模块时,往往只需要父类型中的部分字段。Omit<>能让你的类型保持整洁和聚焦:

interface FullProduct { id: string; name: string; description: string; price: number; createdAt: Date; updatedAt: Date; } // 为 UI 表单创建不含元数据的 ProductForm 类型 type ProductForm = Omit<FullProduct, "id" | "createdAt" | "updatedAt">; const formData: ProductForm = { name: "Gaming Laptop", description: "A powerful laptop for gaming.", price: 1500, };

PickOmit对比

特性PickOmit
用途选择特定字段排除特定字段
语法Pick<Type, Keys>Omit<Type, Keys>
适用场景只需要少量字段时只需要省略少量字段时
示例type A = Pick<Type, "id" \| "name">;type B = Omit<Type, "password">;
结果仅包含idname排除password

一句话概括:Omit<>Pick<>的“相反等价物”,当我们要保留更多、省略更少时,Omit<>更便捷。

从源码看Omit<>在 refine 中的真实应用

Omit<>不只是教程里的玩具,它在 refine 这类大型开源框架的源码中无处不在。下面几个例子都来自当前仓库,可以帮你直观理解它在真实工程中的价值。

裁剪 react-query 的useQuery选项

在 packages/core/src/hooks/data/useOne.ts#L50-L69 中,useOnehook 的queryOptions是这样定义的:

queryOptions?: Omit< UseQueryOptions< GetOneResponse<TQueryFnData>, TError, GetOneResponse<TData> >, "queryKey" | "queryFn" > & { // Make queryKey and queryFn optional queryKey?: UseQueryOptions< GetOneResponse<TQueryFnData>, TError, GetOneResponse<TData> >["queryKey"]; queryFn?: UseQueryOptions< GetOneResponse<TQueryFnData>, TError, GetOneResponse<TData> >["queryFn"]; };

这里 refine 用Omit<UseQueryOptions<...>, "queryKey" | "queryFn">从 TanStack Query 的UseQueryOptions类型中剔除queryKeyqueryFn(因为这两个值由 hook 内部根据resourceid等参数自动计算),随后再通过交叉类型把这两个字段以可选形式放回去。这是"继承第三方类型、约束内部字段"的经典写法。

裁剪useMutation选项

同理,在 packages/core/src/hooks/data/useCreate.ts#L79-L87 中,useCreatemutationOptions通过Omit<UseMutationOptions<...>, "mutationFn">把 TanStack Query 的mutationFn移除(它由 refine 内部绑定到dataProvider.create),只允许调用方传入其余配置:

mutationOptions?: Omit< UseMutationOptions< CreateResponse<TData>, TError, UseCreateParams<TData, TError, TVariables>, unknown >, "mutationFn" >;

useOneuseCreate所在的数据 hooks 目录见 packages/core/src/hooks/data,类似的Omit<>用法还出现在useUpdateuseDeleteuseCustomuseInfiniteList等多个 hooks 中。

改写Paginationmode字段

在 packages/core/src/hooks/useSelect/index.ts#L111-L119 中,useSelectpagination参数被定义为:

pagination?: Prettify< Omit<Pagination, "mode"> & { /** * Whether to use server side pagination or not. * @default "off" */ mode?: Pagination["mode"]; } >;

refine 先用Omit<Pagination, "mode">去掉Pagination类型里的mode字段,再把它重新声明为可选并附上文档注释。这样既保留了原有字段,又让内部受控的字段对外暴露为可选项——Omit<>在此扮演了"类型重构"的角色。

精简按钮组件的 props

在 packages/chakra-ui/src/components/buttons/types.ts#L17-L25 中,Chakra UI 适配层的按钮类型也大量使用Omit<>

export type ShowButtonProps = Omit< RefineShowButtonProps< ButtonProps, { svgIconProps?: Omit<IconProps, "ref">; } >, "ignoreAccessControlProvider" >;

这里有两层Omit

  1. 外层Omit<RefineShowButtonProps<...>, "ignoreAccessControlProvider">从 UI 类型定义中移除框架内部使用的ignoreAccessControlProvider字段,避免暴露给外部使用者;
  2. 内层Omit<IconProps, "ref">从 Tabler Icons 的IconProps中剔除ref,防止 SVG 图标属性被传入不合法的 ref。

这展示了Omit<>框架封装层的另一个典型用途:隐藏内部实现细节,收紧公开 API 的类型面。

编译期 vs 运行时:为什么 linter 很重要

从上面的源码示例可以再次确认:Omit<>的一切约束都发生在编译期。TypeScript 编译成 JavaScript 后,类型信息会被完全擦除,Omit不会产生任何运行时代码。因此:

  • 类型层面的"删除"不等于运行时的"删除"——像本文开头subscriber.roles = [...]这样的赋值在运行时依然有效;
  • 若要在运行时真正剔除敏感字段,需要配合destructuringpick/omit之类的工具函数或序列化逻辑;
  • 开发期要依靠 TypeScript 编译检查与 linter 规则(例如@typescript-eslint的相关配置)来确保 omit 约束不被绕过。

这也是 refine 在源码中大量使用Omit<>做 API 面收紧的底气:类型错误可以在 CI/编辑器里第一时间暴露,而不会等到运行时才发现字段泄漏。

结语

本文围绕Subscriber实体的派生,完整演示了Omit<Type, Keys>的用法:它接受基础类型与待删除键的联合类型,返回剔除这些属性后的新类型;基础类型可以是type也可以是interface;当需要省略的属性多于保留的属性时应改用Pick<>;敏感字段剔除、API 数据过滤、UI 表单类型精简都是它的经典应用场景。随后我们通过 refine 源码中的useOneuseCreateuseSelect与 Chakra UI 按钮类型,看到了它在真实框架中"裁剪第三方类型、隐藏内部字段、收紧公开 API"的实战价值。

最后务必牢记:Omit<>是编译期工具而非运行时防护,Pick<>Omit<>是一对互补的伙伴——保留少用Pick,省略少用Omit。本系列的下一篇将介绍利用Partial<Type>进行对象类型转换。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询