他人案例一(优秀!)
先指出问题所在:
enum ShapeType {
Square = 1,
Circle = 2
}
interface Square {
size: number;
}
interface Circle {
radius: number;
}
type Shape = {
type: ShapeType;
data: Square | Circle;
};
const shape: Shape = {
type: ShapeType.Square,
data: {
// 即使 type 指定了 Sqare,这里仍允许出现 radius,而我们往往希望只允许出现 size
},
};
然后给出 | 解决方案:
enum ShapeType {
Square = 1,
Circle = 2
}
interface Square {
size: number;
}
interface Circle {
radius: number;
}
// type Shape = {
// type: ShapeType;
// data: Square | Circle;
// };
type Shape = {
[Type in ShapeType]: {
type: Type,
data:
Type extends ShapeType.Square ?
Square :
Type extends ShapeType.Circle ?
Circle :
never;
};
}[ShapeType];
const circle: Shape = {
type: ShapeType.Circle,
data: {
// 🌟现在这里就只出现了 radius 类型
},
};
const square: Shape = {
type: ShapeType.Square,
data: {
// 🌟现在这里就只出现了 size 类型
},
};
接着使用类型参数,更优雅地解决需求:
enum ShapeType {
Square = 1,
Circle = 2
}
interface Square {
size: number;
}
interface Circle {
radius: number;
}
// type Shape = {
// type: ShapeType;
// data: Square | Circle;
// };
// type Shape = {
// [Type in ShapeType]: {
// type: Type,
// data:
// Type extends ShapeType.Square ?
// Square :
// Type extends ShapeType.Circle ?
// Circle :
// never;
// };
// }[ShapeType];
type Shape = {
[Type in ShapeType]: {
type: Type,
data: {
[ShapeType.Square]: Square;
[ShapeType.Circle]: Circle;
}[Type];
};
}[ShapeType];
const circle: Shape = {
type: ShapeType.Circle,
data: {
// ✨现在这里就只出现了 radius 类型
},
};
const square: Shape = {
type: ShapeType.Square,
data: {
// ✨现在这里就只出现了 size 类型
},
};
同时还进行举一反三,实现让字段类型依赖 同级 的另一个字段
enum ShapeType {
Square = 1,
Circle = 2
}
interface Square {
size: number;
}
interface Circle {
radius: number;
}
type Shape = {
[Type in ShapeType]: {
type: Type,
} & {
[ShapeType.Square]: Square;
[ShapeType.Circle]: Circle;
}[Type];
}[ShapeType];
const circle: Shape = {
type: ShapeType.Circle,
// 这里只出现 radius
};
const square: Shape = {
type: ShapeType.Square,
// 这里只出现 size
};
最后给出完美解决方案,封装成一个可复用的工具类型:
enum ShapeType {
Square = 1,
Circle = 2
}
interface Square {
size: number;
}
interface Circle {
radius: number;
}
type MutableRecord<U> = {
[SubType in keyof U]: {
type: SubType;
data: U[SubType]
};
}[keyof U];
type Shape = MutableRecord<{
[ShapeType.Square]: Square;
[ShapeType.Circle]: Circle;
}>;
const circle: Shape = {
type: ShapeType.Circle,
data: {
// 这里只出现 radius
}
};
const square: Shape = {
type: ShapeType.Square,
data: {
// 这里只出现 size
}
};
他人案例二,一般,可读性不好,有关 infer
type Test = {
key: "id";
id: number;
} | {
key: "action";
action: string;
}
const test: Test = {
key: 'id',
// 只出现 id,类型为 number
}
const test2: Test = {
key: 'action',
// 只出现 action,类型为 string
}
type IData = {
id: number;
action: string;
};
type ITest<T> = keyof T extends infer U ? U extends keyof T ? { key: U, value: T[U] } : never : never
const test1: ITest<IData> = {
key: 'id',
value: // 类型提示数字
}
const test2: ITest<IData> = {
key: 'action',
value: // 类型提示字符串
}
解决我的实际需求
我的需求是,一份问卷中有多种类型的问题,比如主观题,单项选择题,那么不同类型的问题肯定对应不同的内容格式。
下面是抽离后的简单代码,麻雀虽小肝胆俱全:
enum SurveyQuestionType {
INPUT_CONTENT = 'input_content',
SINGLE_SELECT = "single_select"
}
type SurveyQuestion = {
[Type in SurveyQuestionType]: {
order: number,
questionType: Type,
questionContent: {
[SurveyQuestionType.INPUT_CONTENT]: InputContent,
[SurveyQuestionType.SINGLE_SELECT]: SingleContent
}[Type]
};
}[SurveyQuestionType];
type InputContent = {
title: string,
}
type SingleContent = {
titles: string[],
options: string[]
}
const survey: SurveyQuestion[] = [
{
order: 1,
questionType: SurveyQuestionType.INPUT_CONTENT,
questionContent: { title: '问题一:xxxx' }
},
{
order: 2,
questionType: SurveyQuestionType.SINGLE_SELECT,
questionContent: {
titles: ['问题二:xxx', '问题三:xxxx'],
options: ['A: xx', 'B: xx', 'C: xx', 'D: xx']
}
}
]
参考文章: