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

如何创建类型安全的递归类型?TS树形结构类型定义问题

Fixing Your TypedTree Type with Required Schema Compliance

Let's break down why your current type isn't behaving as expected, then fix it.

The Problem with Your Original Type

Your TypedTree uses a union type (|) between the root schema and a record of child nodes. Union types mean "either/or"—so an empty object {} is valid because it matches the second branch ({ [key: string]: TypedTree<TChildSchema> }, since empty objects are valid for index signatures). That's why your @ts-expect-error test isn't failing like you want.

You actually need an intersection type (&) here: nodes must satisfy the schema and can optionally have additional child nodes (with keys not defined in the schema).

The Fixed Type

Using TypeScript's index remapping (available in 4.4+), we can exclude the schema's keys from the child node index signature to avoid type conflicts:

type TypedTree<TRootSchema, TChildSchema = TRootSchema> = TRootSchema & {
  // Allow any string key except those already in the root schema
  [K in string as Exclude<K, keyof TRootSchema>]: TypedTree<TChildSchema>;
};

const $typeTest = <T>(v: T) => v;

// @ts-expect-error Required properties must be present (this will now fail as expected!)
$typeTest<TypedTree<{ a: number }>>({ }) 

// Valid cases:
$typeTest<TypedTree<{ a: number }>>({ a: 5 }) // Root node with only required property
$typeTest<TypedTree<{ a: number }>>({ a: 5, child: { a: 10 } }) // Root + child node
$typeTest<TypedTree<{ a: number }, { b: string }>>({ a: 5, nested: { b: "test" } }) // Root with custom child schema

How This Works

  1. Intersection with Schema: TRootSchema & ... ensures every node (root or child) must include all required properties from the schema. Empty objects fail because they're missing those required fields.
  2. Index Remapping: [K in string as Exclude<K, keyof TRootSchema>] creates an index signature for any string key not defined in the schema. This prevents conflicts between schema properties and child node keys, while letting you add as many child nodes as needed.
  3. Default Child Schema: The TChildSchema = TRootSchema default means child nodes use the same schema as the root unless you specify otherwise.

Additional Notes

  • If you need optional properties in your schema, just mark them as optional in the schema type (e.g., { a: number, b?: string }). The type will respect optional/required status automatically.
  • This approach ensures every node (root and all descendants) adheres to their respective schema's required/optional rules.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:34:36