news 2026/10/2 7:54:48

type-challenges 中等题解:OmitByType —— 按属性类型(而非键名)精准剔除对象字段

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
type-challenges 中等题解:OmitByType —— 按属性类型(而非键名)精准剔除对象字段
  • 示例工程

【免费下载链接】type-challenges

Collection of TypeScript type challenges with online judge

项目地址:https://gitcode.com/GitHub_Trending/ty/type-challenges
点击查看免费下载

导读

OmitByType是 type-challenges 题库中的一道中等难度(medium)对象类型体操题,编号 2852,收录于仓库 questions/02852-medium-omitbytype/。它的核心诉求与内置工具类型Omit<T, K>截然不同:后者按键名集合剔除属性,而OmitByType要求按值类型剔除属性——凡值类型可赋值给U的键全部过滤掉,其余键原样保留。读完本文你将掌握"键重映射(key remapping)+ 条件类型"这一对象过滤套路,能够独立写出通过全部测试用例的题解,并理解它与姊妹题PickByType之间的镜像关系。

一、题目原文与核心目标

题面原文(摘自 README.md)非常精简:

FromT, pick a set of properties whose type are not assignable toU.

即:从对象类型T中,挑出值类型不可赋值给U的那些属性,组成新的对象类型。

题面给出的示例:

type OmitBoolean = OmitByType<{ name: string count: number isReadonly: boolean isEnable: boolean }, boolean> // { name: string; count: number }

示例中isReadonly与isEnable的值类型是boolean,可赋值给参数U = boolean,因此被剔除;name: string与count: number不可赋值给boolean,得以保留。

题目的难度、标签与元信息记录在 info.yml 中:

difficulty: medium title: OmitByType tags: object author: github: jiangshanmeta name: jiangshan related: 2595

可以看到它属于object(对象)标签系列,作者是 jiangshan,并且官方将其与 2595 号题PickByType标记为关联题目(related)。这与本仓库的目录组织一致:每个题目一个独立目录,包含README.md、template.ts(待补全的模板)、test-cases.ts(官方测试用例)与info.yml(元信息),题库总入口见根目录 README.md。

二、入手前的关键区分:Omit vs OmitByType

在动手前,必须先厘清它与内置工具类型Omit<T, K>的本质差异:

  • Omit<T, K>的第二个参数是键名(字面量类型的联合),剔除依据是"键名在不在 K 里"。本仓库对应的手写练习见 questions/00003-medium-omit/。
  • OmitByType<T, U>的第二个参数是值类型,剔除依据是"T[K]是否可赋值给U"。它不关心键叫什么名字,只关心值的类型形态。

这种差异也决定了实现思路:内置Omit只需一次Exclude<keyof T, K>拿到保留键集合,再交给Pick;而OmitByType必须先按值类型筛选键——这正是本题的考点所在。

三、模板文件与官方测试用例

题目起点是 template.ts,内容仅为占位实现:

type OmitByType<T, U> = any

我们后续的解答就是要替换掉这个any,使其通过 test-cases.ts 中的全部断言。官方测试用例原文如下:

import type { Equal, Expect } from '@type-challenges/utils' interface Model { name: string count: number isReadonly: boolean isEnable: boolean } type cases = [ Expect<Equal<OmitByType<Model, boolean>, { name: string, count: number }>>, Expect<Equal<OmitByType<Model, string>, { count: number, isReadonly: boolean, isEnable: boolean }>>, Expect<Equal<OmitByType<Model, number>, { name: string, isReadonly: boolean, isEnable: boolean }>>, ]

三个用例分别以boolean、string、number作为过滤类型U,覆盖了"多个键被剔除""单个键被剔除"等场景。其中Equal与Expect来自工作区依赖包@type-challenges/utils,其定义位于 utils/index.d.ts:Expect<T extends true> = T用于强制类型恒等于true,而Equal通过一对函数签名的条件类型比较实现严格的类型等价判定,因此解答必须做到结构逐字一致(例如键的顺序、可选性、readonly 修饰符),不能只满足"可赋值"这种宽松关系。

四、题解一:先筛键、再 Pick(经典 Exclude 思路)

最直观的思路分两步:先找出"值类型可赋值给U"的键并剔除,得到保留键的联合;再用内置Pick<T, K>从原对象中取出这些键。

第一步需要用到条件类型 + 映射类型的键查询技巧。对keyof T中的每个键K,判断T[K] extends U:

type KeysNotAssignableTo<T, U> = { [K in keyof T]: T[K] extends U ? never : K }[keyof T]

这段代码的含义是:把T逐键映射成一个新对象,值为never或键名K,再通过索引访问[keyof T]把所有值取并集。由于never在联合类型中会被自动吸收,最终结果就只剩下"不可赋值给U"的键名的联合。

拿到保留键后,交给内置Pick即可:

type OmitByType<T, U> = Pick<T, KeysNotAssignableTo<T, U>>

以U = boolean为例:isReadonly、isEnable两个键被映射为never,KeysNotAssignableTo得到'name' | 'count',Pick<T, 'name' | 'count'>便输出{ name: string; count: number },与题面示例及第一个测试用例完全吻合。

五、题解二:键重映射(key remapping)一步到位

TypeScript 4.1 引入的键重映射(as子句)允许在映射类型中改写键名,把键映射为never即可从结果类型中删除该键。基于此,可以直接在一个映射类型内完成筛选,无需先算键联合:

type OmitByType<T, U> = { [K in keyof T as T[K] extends U ? never : K]: T[K] }

对于每个键K:若T[K] extends U成立,则把键重映射为never(键被删除);否则保留键名K并携带原值类型T[K]。这套写法更紧凑,也是社区中最常见的解答。从语法机制看,它与题解一在语义上等价——都是"保留值类型不可赋值给U的键",只是把"先算键联合、再 Pick"的两步压缩进了一次映射。

对照template.ts中type OmitByType<T, U> = any的占位,将any替换为上述任一实现即可让测试用例通过。

六、深入理解:extends判定与字面量类型陷阱

理解"不可赋值(not assignable)"的判定边界是本题的关键。T[K] extends U走的是 TypeScript 的结构可赋值性规则:

  • 宽类型匹配:boolean类型属性会被U = boolean命中(如示例中的两个布尔字段);
  • 字面量类型也属于可赋值:若某属性的值是字面量类型true,由于true extends boolean成立,它同样会被U = boolean剔除——这正是题面用 "assignable" 而非 "equal" 措辞的原因,也是与"按精确类型匹配"类题目(如按Equal严格比较的题)的关键区别;
  • 联合类型按整体判定:string | number这类联合是否命中U,取决于联合整体能否赋值给U(需要每个成员都可赋值),而不是按成员逐个拆分。

从 test-cases.ts 的用例可以看出,官方测试只覆盖了三种原始类型作为U,并未覆盖字面量或联合类型边界;在自行验证扩展用例时,可以把上述边界作为补充自测点,例如:

type Case = OmitByType<{ a: true, b: boolean, c: string }, boolean> // 结果应为 { c: string },因为 true 与 boolean 都可赋值给 boolean

七、姊妹题对比:OmitByType 与 PickByType

本题的关联题 questions/02595-medium-pickbytype/ 是PickByType——保留可赋值给U的属性,恰好是本题的反向操作:

FromT, pick a set of properties whose type are assignable toU.

其示例与本题共用同一个Model形状,只是结果互补:

type OnlyBoolean = PickByType<{ name: string count: number isReadonly: boolean isEnable: boolean }, boolean> // { isReadonly: boolean; isEnable: boolean; }

对比两者的 test-cases.ts 与本题 test-cases.ts 可以发现:对同一Model,PickByType<Model, boolean>得到{ isReadonly: boolean; isEnable: boolean },而OmitByType<Model, boolean>得到{ name: string; count: number },两者键集合互为补集。实现上只需要把条件分支对调:

// PickByType:可赋值则保留键 type PickByType<T, U> = { [K in keyof T as T[K] extends U ? K : never]: T[K] } // OmitByType:可赋值则删除键 type OmitByType<T, U> = { [K in keyof T as T[K] extends U ? never : K]: T[K] }

把这两道题放在一起练习,能一举掌握"按类型过滤键"的正反两套写法。

八、如何在本仓库本地验证题解

本仓库采用 pnpm workspace 管理(见根目录 package.json 中的pnpm@8.12.1与@type-challenges/utils: workspace:*),测试依赖包源码位于 utils/。本地验证步骤:

  1. 克隆仓库后在根目录执行pnpm install(需 Node 环境与 pnpm);
  2. 编辑 template.ts,把type OmitByType<T, U> = any替换为上文任一实现;
  3. 用 TypeScript 编译器检查测试文件,例如npx tsc --noEmit questions/02852-medium-omitbytype/test-cases.ts——若类型定义正确,Expect<Equal<...>>全部满足约束,编译无错误即通过。

仓库根目录 tsconfig.json 与 tsconfig.base.json 已配置好编译环境;也可以直接在题目的线上 playground 地址(README 头部徽章链接)粘贴解答运行。

九、小结

OmitByType的核心价值在于把"Omit"的维度从键名扩展到值类型,其标准解法依赖两个 TypeScript 特性:条件类型T[K] extends U完成可赋值性判定,映射类型的键重映射as ... never完成键的剔除。两种实现(先Exclude式筛键再Pick、或一步键重映射)在语义上等价,读者可按习惯选用。若再与 PickByType 成对练习,即可完整掌握对象类型按值类型过滤的整套思路,为后续更复杂的对象型挑战(如按嵌套结构过滤、按修饰符过滤等)打下基础。

  • 示例工程

【免费下载链接】type-challenges

Collection of TypeScript type challenges with online judge

项目地址:https://gitcode.com/GitHub_Trending/ty/type-challenges
点击查看免费下载

相关推荐

上一篇:CANN/PTO-ISA:TGEMV矩阵向量乘指令
下一篇:打造你的个人AI知识管家:qmd本地搜索引擎终极实践指南

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 7:53:37

Obsidian+WorkBuddy+Gitee:AI驱动的个人知识库实战方案

这两年我一直在折腾个人知识库这件事&#xff0c;从 Word 文档堆文件夹&#xff0c;到印象笔记&#xff0c;再到 Notion&#xff0c;工具换了不少&#xff0c;核心痛点始终没变&#xff1a;内容越记越多&#xff0c;用的时候根本找不到&#xff1b;就算找到了&#xff0c;碎片和…

作者头像 李华
网站建设 2026/10/2 7:52:22

openrig 编排方案:统一管理 Claude Code 与 Codex 的模型接入配置

1. openrig 到底想解决什么问题第一次看到openrig这个名字&#xff0c;我下意识把它拆成了 "open" "rig" 两个部分。rig 在工程语境里通常指"装配、搭台、把一堆零件组合成能跑的系统"&#xff0c;而 open 则暗示了开放、可插拔、不绑定单一供应…

作者头像 李华
网站建设 2026/10/2 7:52:06

AI智评等级提升方案:从评分到个性化提分建议的工程实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 7:50:52

SR8201F国产百兆PHY调试实战:从机贴失败到杜邦线救场

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华