You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript类型检查失效求助:如何让检查器拒绝Dog、Cat兼容代码?

解决TypeScript中结构类型导致Dog/Cat被视为等价的问题

嘿,我完全懂你遇到的困扰——TypeScript默认的结构类型系统(也就是你说的鸭子类型)确实会把结构完全一致的类型当成等价的,哪怕它们的名字不一样。这和Rust的标称类型系统差异很大,Rust里只要类型名不同,哪怕结构一模一样也是不同的类型。那咱们来看看怎么在TS里模拟标称类型的效果,让类型检查器拒绝把Cat传给需要Dog的函数。

先还原你遇到的问题场景

首先,假设你的代码大概是这样的:

interface Dog {
  name: string;
  age: number;
}

interface Cat {
  name: string;
  age: number;
}

function adoptDog(dog: Dog) {
  console.log(`Adopted dog: ${dog.name}`);
}

const myCat: Cat = { name: "Whiskers", age: 3 };
adoptDog(myCat); // TypeScript不会报错,因为结构完全匹配

这种情况下TS的结构类型检查会认为Dog和Cat是兼容的,所以不会阻止你把Cat传给adoptDog。接下来给你几种靠谱的解决方案:


方案1:添加唯一符号属性(最简洁的运行时无侵入方式)

给每个类型添加一个唯一的Symbol属性,这样两个类型的结构就会有差异,TS就能区分它们了。而且这个符号属性不会影响运行时的逻辑:

// 创建两个唯一的符号作为类型标记
const DogMarker = Symbol('Dog');
const CatMarker = Symbol('Cat');

interface Dog {
  name: string;
  age: number;
  [DogMarker]: void; // 只有Dog拥有的标记属性
}

interface Cat {
  name: string;
  age: number;
  [CatMarker]: void; // 只有Cat拥有的标记属性
}

// 用辅助函数创建实例,确保标记属性被正确添加
function createDog(name: string, age: number): Dog {
  return { name, age, [DogMarker]: undefined };
}

function createCat(name: string, age: number): Cat {
  return { name, age, [CatMarker]: undefined };
}

function adoptDog(dog: Dog) {
  console.log(`Adopted dog: ${dog.name}`);
}

const myCat = createCat("Whiskers", 3);
adoptDog(myCat); // 现在TypeScript会报错!类型不兼容

方案2:使用品牌类型(Branded Types,通用的标称类型方案)

这是更通用的模式,适合需要区分各种相同结构类型的场景(比如UserID和PostID这类ID类型)。我们可以定义一个工具类型来给普通类型加上“品牌”标记:

// 定义品牌类型工具,B是品牌标识(通常是字符串)
type Brand<T, BrandName> = T & { __brand: BrandName };

// 给Dog和Cat加上不同的品牌
type Dog = Brand<{ name: string; age: number }, 'Dog'>;
type Cat = Brand<{ name: string; age: number }, 'Cat'>;

// 辅助函数创建带品牌的实例
function createDog(name: string, age: number): Dog {
  return { name, age, __brand: 'Dog' } as Dog;
}

function createCat(name: string, age: number): Cat {
  return { name, age, __brand: 'Cat' } as Cat;
}

function adoptDog(dog: Dog) {
  console.log(`Adopted dog: ${dog.name}`);
}

const myCat = createCat("Whiskers", 3);
adoptDog(myCat); // TypeScript报错:Cat不能赋值给Dog

方案3:利用类的私有属性(适合用类的场景)

如果你用类而不是接口来定义Dog和Cat,类的私有属性会自动让TS认为它们是不同的类型——因为私有属性的归属是和类绑定的,哪怕属性名完全一样:

class Dog {
  private _typeMarker: void; // 私有标记属性
  constructor(public name: string, public age: number) {}
}

class Cat {
  private _typeMarker: void; // 同样的属性名,但属于不同类
  constructor(public name: string, public age: number) {}
}

function adoptDog(dog: Dog) {
  console.log(`Adopted dog: ${dog.name}`);
}

const myCat = new Cat("Whiskers", 3);
adoptDog(myCat); // TypeScript直接报错,类型不兼容

和Rust实现的对比

你提到了Rust的实现,正好可以对比下:Rust是天生的标称类型系统,只要类型名称不同,哪怕结构完全一致,编译器也会视为不同的类型。比如你的Rust代码:

struct Dog {
    name: String,
    age: u32,
}

struct Cat {
    name: String,
    age: u32,
}

fn adopt_dog(dog: Dog) {
    println!("Adopted dog: {}", dog.name);
}

fn main() {
    let my_cat = Cat {
        name: "Whiskers".to_string(),
        age: 3,
    };
    adopt_dog(my_cat); // Rust编译直接报错:expected struct `Dog`, found struct `Cat`
}

而TypeScript默认是结构类型,所以需要我们手动添加“标称标记”来让类型检查器区分不同的类型,上面的几种方案都是在模拟Rust标称类型的效果。

内容的提问来源于stack exchange,提问作者Bigomby

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:32:01