TypeScript中能否定义严格二选一联合类型实现属性互斥校验?
你当前的类型定义不会报错的核心原因是,TypeScript的非严格联合类型不会校验分支中未定义的属性:Circle接口仅要求必须存在radius、kind可选,没有限制不能出现sideLength,所以同时带有radius和sideLength的对象会被判定为符合Circle类型,自然不会触发报错。
解决方案
我们可以通过排他联合类型的写法,给每个分支明确标记其他分支专属属性为never,即可实现你需要的校验效果,修改后的类型定义如下:
interface Circle { kind?: "circle"; radius: number; // 标记Square的专属属性不允许在Circle分支出现 sideLength?: never; } interface Square { kind: "square"; sideLength: number; // 标记Circle的专属属性不允许在Square分支出现 radius?: never; } type Shape = Circle | Square;
上述类型完全符合你给出的所有校验规则:
- 携带
kind: 'circle'+radius的对象:匹配Circle类型,校验通过 - 仅携带
radius的对象:匹配Circle类型(kind可选),校验通过 - 携带
kind: 'square'+sideLength的对象:匹配Square类型,校验通过 - 同时携带
sideLength和radius的对象:既不符合Circle(存在不允许的sideLength)也不符合Square(存在不允许的radius),校验报错 - 仅携带
sideLength的对象:不符合Circle(缺少必填radius)也不符合Square(缺少kind: 'square'),校验报错 - 携带
kind: 'square'+radius的对象:不符合Square的radius?: never限制,校验报错 - 携带
kind: 'circle'+sideLength的对象:不符合Circle的sideLength?: never限制,校验报错
实际场景适配说明
对应你提到的styled-componentsas属性场景,你可以按照相同逻辑定义:将默认的button属性作为第一个分支(as?: 'button'),a标签属性作为第二个分支(as: 'a'),互相标记对方的专属属性为never即可实现:不传as时仅允许传入button属性,传as="a"时仅允许传入a标签属性,交叉传入属性时直接触发TS报错。
内容的提问来源于stack exchange,提问作者joshfarrant
相关产品推荐
相关产品推荐

