TypeScript 类型挑战:用 GetReadonlyKeys 精准提取对象中的只读键(type-challenges 5 深度解析)
示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载本文以 type-challenges 仓库中编号 00005、难度为 extreme 的题目《Get Readonly Keys》为核心讲解如何实现一个泛型工具类型GetReadonlyKeysT它能够从任意对象类型 T 中筛选出所有readonly修饰的键并以联合类型返回。读者在读完本文后将掌握映射类型修饰符的保留与剥离规则、readonly在类型兼容性中的特殊行为以及借助Equal做类型同一性判断的高阶技巧从而能够独立完成这一类区分修饰符的类型级编程题目。题目背景一道被标记为 extreme 的对象键工具题本题目位于仓库的 questions/00005-extreme-readonly-keys 目录下由仓库作者 Anthony Fuantfu编写。从 info.yml 可以看到它的元信息标题Get Readonly Keys难度extreme地狱级标签utils、object-keys题目虽短却触及 TypeScript 类型系统中最刁钻的角落之一——readonly修饰符。在绝大多数赋值兼容性检查中TypeScript 会无视只读修饰符这使得判断某个键是否只读变成了一道真正的地狱难度题。题目要求返回对象中只读键的联合类型根据 README.pt-BR.md 的描述我们需要实现一个泛型类型GetReadonlyKeysT它返回对象 T 中所有只读键组成的联合类型。原文给出的示例interface Todo { readonly title: string readonly description: string completed: boolean } type Keys GetReadonlyKeysTodo // expected to be title | description在这个Todo接口中title与description被readonly修饰而completed是可变的因此GetReadonlyKeysTodo的期望结果是title | description。题目目录下的 template.ts 给出了解题起点——一个占位的anytype GetReadonlyKeysT any我们需要用真正的类型逻辑替换掉这个any。测试用例理解题目的完整边界真正定义题目验收标准的是 test-cases.ts 中的断言import type { Equal, Expect } from type-challenges/utils type cases [ ExpectEqualtitle, GetReadonlyKeysTodo1, ExpectEqualtitle | description, GetReadonlyKeysTodo2, ] interface Todo1 { readonly title: string description: string completed: boolean } interface Todo2 { readonly title: string readonly description: string completed?: boolean }两个用例分别验证了两类边界情况用例输入类型期望输出考察点Todo1仅title只读其余可变title必须把可变的description、completed正确排除Todo2title、description只读completed?为可选可变title \| description必须正确处理可选键不能被可选的completed?干扰特别是 Todo2 中的completed?: boolean是一个可选但可变的键——它既有?可选修饰符又没有readonly。一个合格的解法必须能穿透可选的干扰准确识别只读修饰符。前置知识三个必须掌握的类型机制1. 同态映射类型会保留修饰符在 TypeScript 中形如{ [P in K]: T[P] }其中K extends keyof T的映射类型被称为同态映射类型homomorphic mapped type它会自动保留源类型属性上的readonly与可选修饰符。这正是内置工具类型Pick、Readonly等得以工作的底层机制。2.-readonly与-?显式剥离修饰符映射类型允许通过加减运算符显式控制修饰符-readonly剥离只读修饰符把属性变为可变-?剥离可选修饰符把属性变为必选。因此{ -readonly [P in K]: T[P] }无论源属性原本是否只读产出的属性都一定是可变的。3.readonly在赋值兼容性中被忽略这是本题最核心的陷阱TypeScript 的结构类型系统在检查可赋值性时不区分{ readonly a: string }与{ a: string }。也就是说只读对象可以赋给可变类型反之亦然。因此诸如K extends keyof T之类的普通条件类型判断完全无法感知readonly的存在。这正是题目被标记为 extreme 的根本原因。解题核心用 Equal 做类型同一性判断既然extends无法区分只读与可变就必须借助更严格的类型相等测试。仓库在 utils/index.d.ts 中提供了现成的Equal工具类型export type EqualX, Y (T() T extends X ? 1 : 2) extends (T() T extends Y ? 1 : 2) ? true : falseEqual的原理是把 X、Y 分别放进泛型函数签名的条件类型中利用条件类型在泛型函数中的延迟求值与参数位置的逆变检查对两个类型做比extends严格得多的同一性比较。它能够识别readonly修饰符的差异。解题思路可以概括为三步遍历T的所有键K对每个键构造两个影子类型一个保留该键原本的只读状态{ [P in K]: T[P] }一个强制剥离只读{ -readonly [P in K]: T[P] }用Equal比较两者若相等说明该键本来就是可变的输出never若不相等说明原键带readonly修饰符输出K。完整实现与逐行拆解基于上述思路一个通过全部测试用例的实现如下import type { Equal } from type-challenges/utils type GetReadonlyKeysT { [K in keyof T]-?: Equal { [P in K]: T[P] }, { -readonly [P in K]: T[P] } extends true ? never : K }[keyof T]逐行拆解[K in keyof T]-?遍历 T 的所有键。-?的作用是剥离映射结果的可选修饰符——因为 Todo2 中completed?是可选的如果不加-?条件为真时输出的never会与可选修饰符纠缠导致整个对象的取值结果被undefined污染无法得到干净的联合类型。这一点正是 Todo2 用例存在的原因。{ [P in K]: T[P] }同态映射保留键 K 在 T 上的真实修饰符。若 K 原本只读此处即为只读属性若 K 原本可变此处即为可变属性。{ -readonly [P in K]: T[P] }显式剥离readonly得到对应的恒为可变版本。EqualA, B extends true ? never : K对只读键A保留只读与B剥离只读在Equal看来不相等返回K对可变键A与B完全相同返回never。末尾的[keyof T]对映射结果整体取索引把所有K收集为联合类型同时自动剔除never。用题目中的 Todo 类型代入验证interface Todo { readonly title: string readonly description: string completed: boolean } type Keys GetReadonlyKeysTodo // title | description ✓对title键Equal{ readonly title: string }, { title: string }结果为false输出title对completed键两个影子类型都是{ completed: boolean }Equal结果为true输出never最终被[keyof T]索引后自然消失。测试用例的通过逻辑回到 test-cases.ts上述实现会得到GetReadonlyKeysTodo1titledescription、completed均为可变键被排除GetReadonlyKeysTodo2title | descriptioncompleted?虽是可选键但-?已消除可选修饰符干扰且它本身可变被正确排除。两条断言ExpectEqual...均由Equal精确校验编译期通过即表示解法正确。在仓库中运行与验证本仓库是一个 pnpm workspace见根目录 package.jsonpackageManager: pnpm8.12.1测试用到的type-challenges/utils通过workspace:*协议直接引用 utils 目录其类型定义即 utils/index.d.ts。验证方式在 template.ts 中填入你的实现然后在仓库根目录执行pnpm install npx tsc --noEmit项目根 tsconfig.json 继承了 tsconfig.base.jsonstrict: true、lib: ESNext、module: commonjs配合 test-cases.ts 中ExpectEqual...的断言写法只要类型不匹配编译就会直接报错全部断言通过即代表GetReadonlyKeysT实现正确。延伸思考为何不能用普通的 extends 判断一个常见的错误解法是// ❌ 错误示范readonly 在赋值兼容性中被忽略 type GetReadonlyKeysT { [K in keyof T]: { readonly [P in K]: T[P] } extends { [P in K]: T[P] } ? K : never }[keyof T]由于{ readonly a: string }与{ a: string }在结构类型系统中互相可赋值上面的extends永远为true结果会把所有键都当成只读键返回完全错误。这正是Equal与普通extends的本质区别前者做的是同一性检查后者做的是可赋值性检查。理解了这一点也就理解了这道 extreme 题目的全部精髓——类型系统的修饰符语义只有在最严格、最深层的检查中才会显形。赞分享示例工程【免费下载链接】type-challengesCollection of TypeScript type challenges with online judge项目地址https://gitcode.com/GitHub_Trending/ty/type-challenges点击查看免费下载相关推荐Matter (connectedhomeip) 持久化键值存储 KVS APIpersistent-storage 示例的构建、运行与平台实现差异Matter connectedhomeip 持久化键值存储 KVS APIpersistent storage 示例的构建、运行与平台实现差异 本文以 Ma示例工程Kubernetes Goat 场景一实战从 .git 暴露到 TruffleHog 检测代码库中的敏感密钥Kubernetes Goat 场景一实战从 .git 暴露到 TruffleHog 检测代码库中的敏感密钥 本文围绕 Kubernetes Goat 的第一示例工程golang-jwt/jwt v5 迁移指南解析选项、Claims 接口重构与错误模型升级inngest 仓库实战golang jwt/jwt v5 迁移指南解析选项、Claims 接口重构与错误模型升级inngest 仓库实战 本指南以 golang jwt/jwt示例工程上一篇抖音网页版直播数据抓取实战告别复杂配置一键获取实时弹幕下一篇Windows远程桌面多用户连接免费方案RDP Wrapper Library完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

tldr 别名页机制深度解析:以阿拉伯语页 `pages.ar/common/..md` 为例

tldr 别名页机制深度解析:以阿拉伯语页 `pages.ar/common/..md` 为例

文档教程知识库 【免费下载链接】tldr Collaborative cheatsheets for console commands 📚. 项目地址: https://gitcode.com/GitHub_Trending/tl/tldr 点击查看 免费下载 本文以 tldr 仓库中的阿拉伯语别名页 pages.ar/common/..md 为切入点&#xff0…

2026/9/30 6:56:07 阅读更多 →
wampee如何配置网站

wampee如何配置网站

1.Wampee-3.1.0\bin\apache\apache2.4.33\conf\extra\httpd-vhosts.conf 这里可以配多个域名&#xff0c;修改对应的端口和网站的源码指向<VirtualHost *:88>ServerAdmin webmasterdummy-host.example.comDocumentRoot "E:/Wampee-3.1.0-beta-3.5/www/zw/public&qu…

2026/9/30 6:56:07 阅读更多 →
万物演化论24(第六章) 从一套房子,看见它所依附的整个系统

万物演化论24(第六章) 从一套房子,看见它所依附的整个系统

24&#xff5c;从一套房子&#xff0c;看见它所依附的整个系统在讨论家庭资产配置时&#xff0c;我们很容易把注意力集中在具体的资产上。买房时研究户型、楼层、面积、单价和贷款利率&#xff1b;卖房时关注挂牌价、成交价、租售比和市场行情。这些信息当然重要&#xff0c;但…

2026/9/30 6:56:07 阅读更多 →

最新新闻

FinOps落地实战:云成本管理与优化体系搭建指南

FinOps落地实战:云成本管理与优化体系搭建指南

FinOps 这个概念这几年在圈子里出现的频率越来越高&#xff0c;但你要是问十个人它到底是个什么东西&#xff0c;可能九个人的回答都不一样。有人说是云成本管理&#xff0c;有人说是一套财务流程&#xff0c;还有人干脆觉得就是个新造的 Buzzword。以我自己在甲方和乙方都摸爬…

2026/9/30 7:39:24 阅读更多 →
文件或目录损坏且无法读取?先别急着格式化,正确抢救顺序全解析

文件或目录损坏且无法读取?先别急着格式化,正确抢救顺序全解析

“文件或目录损坏且无法读取”——这句话大概是Windows用户最不想见到的弹窗之一。前几天一位朋友急匆匆找我&#xff0c;说移动硬盘打不开&#xff0c;弹窗里正是这句提示。他在网上搜了一圈&#xff0c;看到有人建议用chkdsk /f直接修&#xff0c;立刻照做&#xff0c;然后更…

2026/9/30 7:39:24 阅读更多 →
AI代码审计如何高效定位漏洞?用项目背景武装Cursor

AI代码审计如何高效定位漏洞?用项目背景武装Cursor

干了这么多年代码审计&#xff0c;我发现一个特别扎心的现象&#xff1a;很多人把 Cursor 当成一个“什么都会读”的安全专家&#xff0c;结果喂进去一堆代码之后&#xff0c;得到的反馈要么是“请确保使用参数化查询”这种教科书式废话&#xff0c;要么就是对着无关紧要的变量…

2026/9/30 7:39:24 阅读更多 →
计算机网络课设核心:TCP socket编程与协议设计实战

计算机网络课设核心:TCP socket编程与协议设计实战

简介&#xff1a;本资源是一份面向计算机网络课程设计的电子图书馆系统完整设计方案&#xff0c;适用于高校网络工程、信息安全等专业学生完成大型课设或综合实训。方案严格依据课程要求构建可接入Internet的电子图书馆网络架构&#xff0c;涵盖100终端节点、千兆主干与百兆接入…

2026/9/30 7:39:24 阅读更多 →
故障树分析驱动预测性维护:从数据采集到风险决策的落地实践

故障树分析驱动预测性维护:从数据采集到风险决策的落地实践

设备维护这行干得久了&#xff0c;都会遇到一个让人头疼的场景&#xff1a;设备明明是“健康”的&#xff0c;结果说停就停&#xff0c;产线一断&#xff0c;订单延误&#xff0c;维修人员半夜被叫起来抢修&#xff0c;备件还没库存。事后翻记录&#xff0c;发现早就有异常信号…

2026/9/30 7:39:24 阅读更多 →
TCP/IP与OPC协议解析:传输通道与业务语言的关系

TCP/IP与OPC协议解析:传输通道与业务语言的关系

前阵子在帮一家制造企业梳理产线数据上云的方案&#xff0c;方案评审时甲方技术负责人问了一句&#xff1a;设备支持OPC UA&#xff0c;网关也支持TCP/IP&#xff0c;那我到底该按哪个协议接&#xff1f;当时会议室里七八个人都没反应过来——这问题本身就把两层东西搅在一起了…

2026/9/30 7:38:23 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述&#xff1a;为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多&#xff0c;后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表&#xff0c;动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介&#xff1a;本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档&#xff0c;聚焦城市公共广告资源信息化管理痛点&#xff0c;提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构&#xff0c;含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求&#xff0c;背景很直接&#xff1a;公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关&#xff0c;开放给几个业务团队用。结果第一个月账单出来&#xff0c;额度直接超了 4 倍。仔细查日志&#xff0c;发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集&#xff1a;Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/29 8:16:59 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/29 8:24:48 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践&#xff1a;原型怎样变成可用功能分类&#xff1a;[AI/大模型]细分主题&#xff1a;AI 增强型 CI/CD 流水线自动化与 GitOps 实践&#xff1a;Agent 工作流、工具调用与任务拆解&#xff1a;从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战&#xff1a;复盘记录怎样真正派上用场分类&#xff1a;[工程技术]细分主题&#xff1a;Kubernetes 生产环境运维与排障实战&#xff1a;可复制的项目复盘模板与决策记录大部分团队的事故复盘报告&#xff0c;最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理&#xff1a;核心链路应该先拆哪一步分类&#xff1a;[工程技术]细分主题&#xff1a;Docker 容器化技术与镜像安全管理&#xff1a;核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用&#xff08;包含 Web 接口、后台…

2026/9/29 3:55:56 阅读更多 →