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

TypeScript中基于另一属性的泛型映射类型:如何确保ScriptElement的type与data类型匹配

Solution: Use a Discriminated Union for ScriptElement

The core issue here is that your original ScriptElement interface doesn't link the type property to the data property—TypeScript sees data as a union of all possible element data types, regardless of what type is set to. To fix this while keeping your elements array flexible (allowing mixed element types), you need to use a discriminated union type instead of a generic interface.

Step-by-Step Fix

You can reuse your existing type definitions to build a clean, type-safe union that enforces matching between type and data:

enum ElementType { Text = 'text', Rating = 'rating', Picture = 'picture' }

type TextData = { title: string; description: string; boldTitle: boolean; };
type RatingData = | { question: string; type: 'number'; scale: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; minLabel: string; maxLabel: string; } | { question: string; type: 'star' | 'emoji'; scale: 2 | 3 | 4 | 5; minLabel: string; maxLabel: string; };
type PictureData = { url: string; };

// Keep your existing ElementData utility type
type ElementData<Type extends ElementType> = 
  Type extends ElementType.Text ? TextData :
  Type extends ElementType.Rating ? RatingData :
  Type extends ElementType.Picture ? PictureData :
  Record<string, never>;

// Rewrite ScriptElement as a discriminated union
type ScriptElement = {
  [Type in ElementType]: {
    name: string;
    type: Type;
    data: ElementData<Type>;
  };
}[ElementType]; // Extract the union of all mapped element types

// ElementCategory stays unchanged (now accepts mixed element types)
type ElementCategory = { name: string; elements: ScriptElement[]; };

// Default data remains valid and type-aligned
const defaultElementsData: { [Type in ElementType]: ElementData<Type> } = { 
  [ElementType.Text]: { title: '', description: '', boldTitle: false, }, 
  [ElementType.Rating]: { question: '', type: 'number', scale: 10, minLabel: '', maxLabel: '', }, 
  [ElementType.Picture]: { url: '', }, 
};

// Now TypeScript enforces type-data matching automatically!
const ContentElementsCategory: ElementCategory = { 
  name: 'Content', 
  elements: [ 
    { name: 'Text', type: ElementType.Text, data: defaultElementsData[ElementType.Text], }, 
    { name: 'Picture', type: ElementType.Picture, data: defaultElementsData[ElementType.Picture], }, 
    { name: 'Rating', type: ElementType.Rating, data: defaultElementsData[ElementType.Rating], }, 
    // Uncomment this line to see the error: data doesn't match the Text type
    // { name: 'Invalid Element', type: ElementType.Text, data: defaultElementsData[ElementType.Picture], },
  ], 
};

Why This Works

  • The ScriptElement type is built by mapping each ElementType to a specific object where type is fixed to that enum value, and data is the corresponding ElementData<Type>. Indexing into this mapped type with [ElementType] converts it into a union of all valid element types.
  • This is a discriminated union: TypeScript uses the type property (the discriminant) to automatically narrow down the allowed data type. Hover over any data property in your elements array now, and you'll see it correctly shows the matching data shape for that element's type.
  • Unlike a generic interface approach, this union lets your elements array contain mixed element types (Text, Rating, Picture) without forcing you to lock the array to a single generic type.

What Was Wrong With the Original Code?

Your original ScriptElement interface had:

interface ScriptElement { 
  name: string; 
  type: ElementType; 
  data: ElementsData[ElementType]; 
}

Here, data is a union of all possible data types (TextData | RatingData | PictureData), with no enforced link to the type property. TypeScript couldn't infer that when type is ElementType.Text, data should only be TextData—it allowed any data type regardless of the type value.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 01:07:44