设置对象的各个属性的函数的类型正确吗?

时间:2019-08-14 18:24:32

标签: typescript

我有一个设置对象,该对象具有几个具有不同类型的不同属性。而且我正在尝试编写如下函数:

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'?键入我描述的函数的正确方法是什么,理想情况下是如此严格,以至于尝试将字符串分配给布尔设置都会失败?

2 个答案:

答案 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]
)

说:

  • nameISettings的属性的名称
  • valueISettings属性的值

但没有说这两种情况下都是相同的属性,使调用者可以自由编写:

updateSettings(isettings, "string_property", true);

...这就是为什么不知道函数主体中的属性分配类型正确,并被编译器拒绝的原因。

相反,签名

updateSettings<K extends keyof ISettings>(
  oldSettings: ISettings,
  name: K,
  value: ISettings[K]
)

表示K的键中有一个单一类型ISettings,因此name的类型均为K,并且{ {1}}的类型为value