在Typescript中通过缩窄为对象值分配类型



我正在尝试动态创建一个设置菜单在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.valueboolean而不是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[]

相关内容

  • 没有找到相关文章

最新更新