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

如何在TypeScript中定义自引用对象类型且避免泛型循环错误?

如何在TypeScript中定义可自引用且类型安全的结构?

我需要创建一种支持自引用的类型,在复用对象部分结构时既能保证类型安全,又能避免代码重复。比如对于复杂结构的对象,只需定义一次,就能通过引用复用,无需复制粘贴,让代码更整洁易维护。

基础示例(使用any)

下面是一种简单的实现方式,但self参数为any类型,无法保证类型安全:

type Type = {
  [key: string]: string | ((self: any) => any);
};

function fn<T extends Type>(config: T): T {
  return config;
}

const cnt = fn({
  key_a: "string",
  key_b: self => self.key_a,
});

这里Type被定义为键为字符串的对象,值可以是字符串或接收any类型self的函数。泛型函数fn确保传入的对象符合Type结构,但self的类型丢失了安全性。

尝试泛型自引用的错误

我尝试用泛型实现类型安全的自引用,但触发了循环引用的类型错误:

type Type<T extends Type<T>> = {
  [key: string]: string | ((self: T) => any);
};

function fn<T extends Type<T>>(config: T): T {
  return config;
}

const cnt = fn({
  key_a: "string",
  key_b: self => self.key_a,
});

错误信息如下:

Type '(self: Type<unknown>) => string | ((self: unknown) => any)' is not assignable to type 'string | ((self: unknown) => any)'.
  Type '(self: Type<unknown>) => string | ((self: unknown) => any)' is not assignable to type '(self: unknown) => any'.
    Types of parameters 'self' and 'self' are incompatible.
      Type 'unknown' is not assignable to type 'Type<unknown>'.

问题在于Type<T extends Type<T>>的循环约束导致TypeScript无法正确推导类型,陷入循环依赖。

解决方法

我们可以调整类型定义,让函数的self参数直接指向当前对象的具体类型T,通过泛型函数的类型推导来避免循环约束:

// 定义Type时,让函数接收的self类型为泛型T
type Type<T> = {
  [key: string]: string | ((self: T) => any);
};

// 泛型函数约束T必须符合Type<T>,此时TypeScript能正确推导T的结构
function fn<T extends Type<T>>(config: T): T {
  return config;
}

const cnt = fn({
  key_a: "string",
  key_b: (self) => self.key_a, // self现在被正确推导为当前对象的类型,类型安全
});

这种写法的核心是:不再让Type自身携带循环泛型约束,而是将self的类型绑定到泛型函数的具体类型参数T上,让TypeScript通过传入的对象自动推导T的结构,从而避免循环引用问题。

实际用例:表单字段复用

动机

在管理面板中,多个表单常需要复用相同字段(如email、password)。如果每个表单重复定义这些字段,哪怕是标签或必填标记的微小修改,都需要在多处修改,增加维护成本和不一致风险。

目标是在sharedFields中一次性定义共享字段,让每个表单通过self引用这些字段,保持Schema遵循DRY原则,同时保留类型安全。

实现代码

type Field = {
  label: string;
  type: string;
  required?: boolean;
};

// 直接让inputs中的函数接收Schema类型的self,无需循环约束
type Schema = {
  sharedFields: {
    email: Field;
    password: Field;
  };
  forms: {
    createUser: {
      inputs: Array<(self: Schema) => Field>;
    };
    inviteUser: {
      inputs: Array<(self: Schema) => Field>;
    };
  };
};

const schema: Schema = {
  sharedFields: {
    email: {
      label: "Email",
      type: "email",
      required: true,
    },
    password: {
      label: "Password",
      type: "password",
      required: true,
    },
  },
  forms: {
    createUser: {
      inputs: [
        self => self.sharedFields.email,
        self => self.sharedFields.password,
      ],
    },
    inviteUser: {
      inputs: [
        self => self.sharedFields.email,
      ],
    },
  },
};

结果

表单通过self复用sharedFields中的字段定义,所有引用都指向单一数据源,既减少了重复代码,又保留了完整的类型安全——修改sharedFields中的字段时,所有引用该字段的表单都会自动同步更新,避免了不一致问题。

内容的提问来源于stack exchange,提问作者Daniel de Andrade Varela

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 07:07:34