我正在尝试动态创建一个设置菜单在TS基于此数据:
const userSettings = {
testToggle: {
title: "Toggle me",
type: "toggle",
value: false,
},
testDropdown: {
title: "Test Dropdown",
type: "dropdown",
value: "Change me",
selectValues: ["Option 1", "Option 2", "Option 3"]
},
testText: {
title: "Test textbox",
type: "text",
value: "Change me",
}
这允许我使用
轻松访问每个用户输入字段userSettings.testToggle.value
我正在努力为userSettings对象做输入。到目前为止,我已经想到了
export type InputFieldTypes = {
toggle: boolean,
text: string,
dropdown: string,
}
type ExtraProperties = Merge<{ [_ in keyof InputFieldTypes]: {} }, {
// Additional properties which will be added to the input field with the same key
dropdown: {
selectValues: string[],
},
}>
type FieldOptions<K, V extends Partial<Record<keyof K, any>>> = {
[P in keyof K]: {
title: string,
type: P,
value: K[P],
} & V[P]
}[keyof K]
export type InputField = FieldOptions<InputFieldTypes, ExtraProperties>;
生成一个很好的并集:
type InputField = {
title: string;
type: "toggle";
value: boolean; } | {
title: string;
type: "text";
value: string; } | ({
title: string;
type: "dropdown";
value: string; } & {
selectValues: string[]; })
正确地将切换值限制为布尔值,并要求下拉菜单具有selectValues属性。然而,将类型{ [key: string]: InputField }
分配给userSettings
太宽了,因为userSettings.testToggle.value
具有类型boolean | string
,并且我想避免强制转换。
我管理的最接近的是一个函数
const asInputFieldObject = <T,>(et: { [K in keyof T]: InputField & T[K] }) => et;
const settings = asInputFieldObject(userSettings);
的行为基本符合预期,强制userSettings对象为InputFields,并允许正确输入settings.darkMode.value
等访问,但会抛出相当无用的错误
Type 'number' is not assignable to type 'never'.
当尝试将一个数字分配给开关而不是布尔值时。有更干净的方法吗?
编辑:
澄清一下,我遇到的问题是专门为userSettings
分配类型,因为我希望userSettings.testToggle.value
是boolean
而不是string | boolean
,就像Record<string, InputField>
或{ [key: string]: InputField }
一样,其他输入也是如此。可以用这种方式为对象分配类型(参见如何让Typescript推断对象的键,但定义其值的类型?),但是简单地分配InputField并不能解决这个问题,因为value
访问仍然具有所有值的联合类型。
asInputFieldObject
是我最近的一次尝试,通过将相交类型添加到映射类型:[K in keyof T]: InputField & T[K]
来修复这个问题。这就产生了一个表单类型:
const userSettings: {
darkMode: {
title: string;
type: "toggle";
value: boolean;
} & {
title: string;
type: "toggle";
value: false;
};
testDropdown: {
title: string;
type: "dropdown";
value: string;
} & {
selectValues: string[];
} & {
...;
}; }
在大多数情况下工作得很好,但是当给value
一个不正确的类型时,它不会直接抱怨错误的类型,而是会因为交集而抛出not assignable to type 'never'.
错误。
如果你有一个InputField
类型的变量,你可以执行类型窄化,这样TypeScript就可以确定哪种类型适合赋值。
var inputField: InputField;
switch (inputField.type) {
case 'toggle':
inputField.value; // is boolean
inputField.value = 1; // Type 'number' is not assignable to type 'boolean'.
break;
case 'text':
inputField.value; // is string
break;
case 'dropdown':
inputField.selectValues; // is string[]
break;
default:
}
编辑:在阅读原始问题之后,您可以像下面这样键入userSettings
…
interface ToggleInputField {
title: string;
type: 'toggle';
value: boolean;
}
interface DropDownInputField {
title: string;
type: 'dropdown';
value: string;
selectValues: string[];
}
interface TextInputField {
title: string;
type: 'text';
value: string;
}
type InputField = ToggleInputField | DropDownInputField | TextInputField;
const userSettings: Record<string, InputField> = {
testToggle: {
title: 'Toggle me',
type: 'toggle', // automatically identified to be ToggleInputField
value: false,
},
testDropdown: {
title: 'Test Dropdown',
type: 'dropdown', // automatically identified to be DropDownInputField
value: 'Change me',
selectValues: ['Option 1', 'Option 2', 'Option 3'],
},
testText: {
title: 'Test textbox',
type: 'text', // automatically identified to be TextInputField
value: 'Change me',
},
};
命名每个输入字段的类型将有助于当你将鼠标悬停在一个字段上时,例如,将鼠标悬停在userSettings.testDropdown.selectValues
字段上将显示类型为DropDownInputField.selectValues: string[]
。