gpt4 book ai didi

javascript - 键入用于将值添加到数组的通用函数,该数组是对象的属性

转载 作者:行者123 更新时间:2023-12-04 03:22:34 25 4
gpt4 key购买 nike

如何键入一个函数,它是一个用于向数组添加值的通用函数?数组是对象的属性。

我需要以某种方式告诉 TypeScript 我传入的键将是一个键,它在给定对象中的值与传入的值的类型相同。

我试过了,但是有错误,

type Wheel = { id: string; }
type Door = { position: string; }

type Car = {
name: string;
value: number;
wheels: Array<Wheel>;
doors: Array<Door>;
}

const car: Car = {
name: 'SuperFasto',
value: 9001,
wheels: [{id: '1'}, {id: '2'}],
doors: [{position: 'frontLeft'}, {position: 'frontRight'}]
}

const updateArrayField = (key: 'wheels' | 'doors', value: Wheel | Door ) => {
if (!car[key]) {
car[key] = [value]; // Type 'Wheel | Door' is not assignable to type 'Wheel & Door'.
return;
}

const currentFields = car[key];
if (!Array.isArray(currentFields)) {
return;
}
if (currentFields.includes(value)) {
const index = currentFields.indexOf(value);
currentFields.splice(index, 1);
car[key] = currentFields;
} else {
currentFields.push(value);
car[key] = currentFields;
}
}

Playground 链接 here

类型'轮| Door' 不可分配给类型 'Wheel & Door'

此外,是否有可能以某种方式以通用方式键入它,这样我就不必明确说明对象 (Car) 的哪些属性将是数组属性?

最佳答案

总结:修复您的调用签名,使其实际上强制执行 keyvalue 之间的相关性,然后在实现中使用类型断言,这样编译器就不会被这种相关性混淆。


现在 key 的类型和 value 的类型之间没有相关性,所以没有什么可以阻止某人使用类型为 updateArrayFieldkey 和类型为 "doors"value 调用 Wheel:67915

>
updateArrayField("doors", { id: 'oops' }); // no error

编译器提示的部分原因是因为您确实有可能使用 Wheel 并尝试将其推送到需要 Door 的对象上。具体的错误是说你要么有一个 Wheel 元素的数组,要么有一个 Door 元素的数组;添加到这样的数组中的唯一安全东西是既是 Wheel 又是 Door : Wheel & Door 的东西。但是您要添加的东西是 或者 Wheel 或者 Door 但不知道两者都是: Wheel | Door 。那是不兼容的。 (有关此十字路口的更多信息,请参阅 TS3.3's introduction of improved behavior for calling union types,这似乎不知从何而来)。

在我们尝试修复错误消息之前,我们应该确保该函数使不正确的调用变得不可能,或者至少更困难。


这是传统上你会使用 overloads 来修复的那种东西:

function updateArrayFieldOverload(key: "wheels", value: Wheel): void;
function updateArrayFieldOverload(key: "doors", value: Door): void;
function updateArrayFieldOverload(key: "wheels" | "doors", value: Wheel | Door) {
// impl
}
updateArrayFieldOverload("doors", { id: 'oops' }); // error! no overload matches this call
updateArrayFieldOverload("wheels", { id: '3' }); // okay
updateArrayFieldOverload("doors", { position: 'backLeft' }) // okay

或者您可以使用 generic 函数:

function updateArrayFieldGeneric<K extends "doors" | "wheels">(key: K, value: Car[K][number]) {
// impl
}
updateArrayFieldGeneric("doors", { id: 'oops' }); // error! {id: string} is not a Door
updateArrayFieldGeneric("wheels", { id: '3' });
updateArrayFieldGeneric("doors", { position: 'backLeft' })

尽管如果您将 K 扩展为完整联合类型,这仍然可以接受奇怪的输入:

updateArrayFieldGeneric(
Math.random() < 0.5 ? "doors" : "wheels",
{ id: 'oops' }
); // oops, no error now

但在这种情况下,我倾向于使用 unionrest tuples :

type KeyValue = [key: "wheels", value: Wheel] | [key: "doors", value: Door];

const updateArrayField = (...[key, value]: KeyValue) => {
// impl
};

updateArrayField("doors", { id: 'oops' }); // error!
updateArrayField("wheels", { id: '3' }); // okay
updateArrayField("doors", { position: 'backLeft' }); // okay
updateArrayField(
Math.random() < 0.5 ? "doors" : "wheels",
{ id: 'oops' }
); // error!

我们甚至可以通过 mapped type 以编程方式从 Car 生成剩余元组的并集,我们立即生成 index into,其属性为 conditional types:

type KeyValue = { [K in keyof Car]-?: 
Car[K] extends Array<infer T> ? [key: K, value: T] : never
}[keyof Car];

// type KeyValue = [key: "wheels", value: Wheel] | [key: "doors", value: Door];

你可以验证这和以前一样,如果你改变 Car 它会自动改变 KeyValue


不幸的是,这些更改都无法解决实现中的错误:

const updateArrayField = (...[key, value]: KeyValue) => {
if (!car[key]) {
car[key] = [value]; // still a Wheel & Door error!!!
return;
}
}

那是,因为编译器确实没有办法模拟两个联合类型值(如 keyvalue)之间的相关性。在 microsoft/TypeScript#30581 有一个 GitHub 问题解释了这个问题,承认似乎没有任何简单的方法可以安全地修复它,并解释说解决方法是编写冗余代码或使用 type assertion

如果您希望它在 Car 更改时自动更改,那么冗余代码是行不通的,但至少编译器可以验证它是安全的:

const updateArrayFieldRedundant = (...kv: KeyValue) => {
if (kv[0] === "doors") {
const [key, value] = kv;
if (!car[key]) {
car[key] = [value]; // no errors
return;
}
} else {
const [key, value] = kv;
if (!car[key]) {
car[key] = [value]; // no errors
return;
}
}
}

类型断言就是告诉编译器你确信你所做的是好的,即使它无法验证。这是我建议的处理方式:

const updateArrayFieldAssertion = (...[key, value]: KeyValue) => {
if (!car[key]) {
(car as Record<KeyValue[0], Array<KeyValue[1]>>)[key] = [value];
return;
}
}

这里我们刚刚说过,我们会将 car 视为在 Array<Wheel | Door>"doors" 键上都持有 "wheels"。这不是真的,但它抑制了错误(因为现在您要将 Wheel | Door 添加到 Wheel | Door 的数组中)。还有其他方法可以进行类型断言;最简单的就是做类似 car[key] = [value] as any 的事情,甚至不尝试为该行获取任何编译器验证的类型安全。

在任何情况下,类型断言都是您将验证类型安全的责任从编译器上移开,并将负担推给您自己。所以双重和三次检查 car[key] = [value] 永远不会做错事。在您的原始版本中它可以,而在上面的版本中它可能不能,但是通过一些测试不会有什么坏处。


Playground link to code

关于javascript - 键入用于将值添加到数组的通用函数,该数组是对象的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68202587/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com