我有一个设置对象,该对象具有几个具有不同类型的不同属性。而且我正在尝试编写如下函数:
update(settings, name, value)
更新name
上的属性settings
并将其设置为value
。我的实际用例有些不同,但这是问题的最简单版本。
我在让Typescript理解我在这里想要做什么时遇到了麻烦,并且在抱怨我的代码:
interface ISettings {
string_setting: string;
boolean_setting: boolean;
}
function updateSettings(
oldSettings: ISettings,
name: keyof ISettings,
value: ISettings[keyof ISettings]
): ISettings {
let newSettings: ISettings = { ...oldSettings };
newSettings[name] = value;
return newSettings;
}
在线
newSettings[name] = value;
我遇到错误
键入“字符串|布尔”不可分配为“从不”类型。 不能将“字符串”类型分配给“从不”类型。ts(2322)
VSCode告诉我newSettings
的类型为ISettings,而name
的类型为“ string_setting” | “布尔值设置”。因此,这看起来应该是正确的,但仍然会引发错误。
我认为value
的类型也不正确,因为它只是设置对象上属性的所有可能类型的并集,因此不能确保值类型与由...选择的特定属性匹配名称参数。
为什么Typescript在我的代码中抱怨'never'?键入我描述的函数的正确方法是什么,理想情况下是如此严格,以至于尝试将字符串分配给布尔设置都会失败?
答案 0 :(得分:1)
我不确定是否是最好的答案,但是我找到了一种适用于泛型类型的解决方案。我在vs代码中进行了测试,它没有任何问题,并且它是由打字稿编译的,没有错误和警告。
interface ISettings {
string_setting: string;
boolean_setting: boolean;
}
function updateSettings<K extends keyof ISettings>(
oldSettings: ISettings,
name: K,
value: ISettings[K]
): ISettings {
let newSettings: ISettings = { ...oldSettings };
newSettings[name] = value;
return newSettings;
}
const settings: ISettings = null;
const updatedSettings1 = updateSettings(settings, 'boolean_setting', false);
const updaatedSettings2 = updateSettings(settings, 'string_setting', 'string value');
更新1
同样,如果不是很明显,则不仅可以为此ISettings接口编写此函数,还可以仅为对象编写此函数。 (T extends {}
不一定,但我不认为您会覆盖不是对象值的内容)
interface ISettings {
string_setting: string;
boolean_setting: boolean;
}
function updateObject<T extends {}, K extends keyof T>(
object: T,
key: K,
value: T[K]
): T {
return { ...object, [key]: value };
}
const initial: ISettings = null;
const updated0: ISettings = updateObject(initial, 'string_setting', 'string value'); // ok
const updated1: ISettings = updateObject(initial, 'string_setting', 123); // error
const updated2: ISettings = updateObject(initial, 'boolean_setting', true); // ok
const updated3: ISettings = updateObject(initial, 'boolean_setting', 'not a boolean value'); // error
更新2: 而且,如果您的函数的输入对象和响应的类型不同,则可以以这种形式编写它(但是我不确定是否可以不编写任何形式,至少我不知道如何写)
interface ISettings {
string_setting: string;
number_settings: number;
boolean_setting: boolean;
}
function updateObject<T extends {}, K extends keyof T, V>(
object: T,
key: K,
value: V
): V extends T[K] ? T : Pick<T, Exclude<keyof T, K>> & Record<K, V> {
return { ...object, [key]: value } as any;
}
const initial: ISettings = null;
const updated0 = updateObject(initial, 'string_setting', 'string value'); // type is: ISettings
const updated1 = updateObject(initial, 'string_setting', 123); // type is: Pick<ISettings, "boolean_setting" | "number_settings"> & Record<"string_setting", number>
const updated2 = updateObject(initial, 'boolean_setting', true); // type is: ISettings
const updated3 = updateObject(initial, 'boolean_setting', 'not a boolean value'); // type is: Pick<ISettings, "string_setting" | "number_settings"> & Record<"boolean_setting", string>
答案 1 :(得分:1)
签名翻译成普通英语
function updateSettings(
oldSettings: ISettings,
name: keyof ISettings,
value: ISettings[keyof ISettings]
)
说:
name
是ISettings
的属性的名称value
是ISettings
属性的值但没有说这两种情况下都是相同的属性,使调用者可以自由编写:
updateSettings(isettings, "string_property", true);
...这就是为什么不知道函数主体中的属性分配类型正确,并被编译器拒绝的原因。
相反,签名
updateSettings<K extends keyof ISettings>(
oldSettings: ISettings,
name: K,
value: ISettings[K]
)
表示K
的键中有一个单一类型ISettings
,因此name
的类型均为K
,并且{ {1}}的类型为value
。