英文:
TypeScript: Filling an Object progressively to match a type/interface
问题
在TypeScript中,是否有一种逐步构建对象以匹配类型或接口的方法,而不会因为从一开始就没有所有预期属性而导致IDE(或编译器?)抱怨?
让我举个例子。我有一组水果,并且我想要操作将值分配给每个水果的对象。
type Fruit = 'apple' | 'lemon' | 'melon';
type FruitMapToString = { [key in Fruit]: string; };
const FRUIT_DESCRIPTIONS: FruitMapToString = {
apple: 'A basic fruit',
lemon: 'An acidic fruit',
melon: 'A refreshing fruit',
} as const;
这是一个可能会修改以添加新水果的文件。我不希望我的代码的其余部分依赖于存在哪些水果的知识,因为我不想更新它。
现在,我想要一个从水果到数字的映射(用于计算我的水果数量)。我想要使用一种类型,该类型暗示所有水果都被包含在内:
type FruitMapToNumber = { [key in Fruit]: number; };
因此,我希望依赖于FRUIT_DESCRIPTIONS
的键来初始化我的对象为:{ apple: 0, lemon: 0, melon: 0 }
。但我无法找到一种方法来做到这一点,而不会在某个时候抱怨某些属性缺失。
通常情况下,我想这样做:
function makeFruitToNumberMap(): FruitMapToNumber {
const map: FruitMapToNumber = {}; // ❌ 缺少属性 'apple' 等
let fruit: Fruit;
for (fruit in FRUIT_DESCRIPTIONS) {
map[fruit] = 0;
}
return map;
}
但它不起作用,因为当初始化为{}
时,我的映射缺少属性。
我还尝试过从键是可选的类型开始:
const map: { [key in Fruit]?: boolean; } = {};
但然后IDE在return
级别抱怨,因为“'number | undefined'
不能分配给类型'number'
”。(尽管在函数结束时,所有水果都被分配为0
。)
我甚至尝试了这个,尽管我不想为了可读性而使用它:
function makeFruitToNumberMap(): FruitMapToNumber {
return Object.fromEntries(
Object.keys(FRUIT_DESCRIPTIONS).map(
(fruit) => [fruit, 0]
)
);
}
再次,IDE抱怨缺少属性。
英文:
In TypeScript, is there a way to progressively build an object to match a type or interface, without having the IDE (or the compiler?) complaining because not all expected attributes are here from the start?
Let me take an example. I have a list of fruits, and I will want to manipulate objects that assign values to each and every fruit.
type Fruit = 'apple' | 'lemon' | 'melon';
type FruitMapToString = { [ key in Fruit ]: string; };
const FRUIT_DESCRIPTIONS: FruitMapToString = {
apple: 'A basic fruit',
lemon: 'An acidic fruit',
melon: 'A refreshing fruit',
} as const;
This is in a file that might be modified to add new fruits. And I don't want the rest of my code to rely on the knowledge of what fruits exists, because I don't want to update it.
Now, I want a map from fruits to number (to count my fruits). And I want to use a type that implies that all fruits are covered:
type FruitMapToNumber = { [ key in Fruit ]: number; };
So, I want to rely on FRUIT_DESCRIPTIONS
's key to initialize my object to: { apple: 0, lemon: 0, melon: 0 }
. But I can't find a way to do that without the IDE complaining at some point that some properties are lacking.
Typically I would like to do that:
function makeFruitToNumberMap(): FruitMapToNumber {
const map: FruitMapToNumber = {}; // ❌ Missing the properties 'apple' etc
let fruit: Fruit;
for ( fruit in FRUIT_DESCRIPTIONS ) {
map[ fruit ] = 0;
}
return map;
}
But it doesn't work, because when initialized to {}
, my map is lacking properties.
I also tried starting with a type where the keys are optional:
const map: { [ key in Fruit ]?: boolean; } = {};
But then the IDE complains at the return
level, because “'number | undefined'
is not assignable to type 'number'
”. (Even thought at the end of the functions, all fruits are assigned to 0
.)
I even tried this, even through I don't want to use that for readability:
function makeFruitToNumberMap(): FruitMapToNumber {
return Object.fromEntries(
Object.keys( FRUIT_DESCRIPTIONS ).map(
( fruit ) => [ fruit, 0 ]
)
);
}
Again, the IDE complains about missing properties.
答案1
得分: 0
以下是代码的中文翻译:
// 可以考虑在这里使用枚举来设置主要类型并用于迭代:
enum Fruit {
apple = "苹果",
lemon = "柠檬",
melon = "西瓜"
}
type TFruit = keyof typeof Fruit; // 与您的字符串联合类型相同
type FruitMapToNumber = { [key in Fruit]: number };
type FruitMapToString = { [key in Fruit]: string };
const FRUIT_DESCRIPTIONS: FruitMapToString = {
[Fruit.apple]: "一种基础水果",
[Fruit.lemon]: "一种酸性水果",
[Fruit.melon]: "一种清新的水果"
};
// 无法创建具有预定义字段的对象。因此,我们只能通过迭代来创建它。但这里有两个不愉快的事情:
// - 第一个是因为它是迭代创建,我们总是从一个**空**对象开始,这意味着最终会有`Partial`类型。
// - 这里的第二个不愉快之处是,js不允许我们传递哪些键将从`Object.keys`方法返回,它总是期望返回`string`键。
function makeFruitToNumberMap() {
const keys = Object.keys(Fruit); // 第二个问题:键的类型是string[]
const map = keys.reduce((acc, key) => ({ ...acc, [key]: 0 }), {}); // 第一个问题:原始对象为空,我们逐步设置值
return map;
}
// 因此,为了解决这个问题,我们可以使用**类型断言**:
function makeFruitToNumberMap() {
const keys = Object.keys(Fruit) as TFruit[];
const map = keys.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
return map as FruitMapToNumber;
}
希望这对您有所帮助!如果您有任何其他问题,请随时问。
英文:
It might be more convinient to use enum here for setting up primary types and use them for iterating:
enum Fruit {
apple = "apple",
lemon = "lemon",
melon = "melon"
}
type TFruit = keyof typeof Fruit; //it's the same as your strings uion type
type FruitMapToNumber = { [key in Fruit]: number };
type FruitMapToString = { [key in Fruit]: string };
const FRUIT_DESCRIPTIONS: FruitMapToString = {
[Fruit.apple]: "A basic fruit",
[Fruit.lemon]: "An acidic fruit",
[Fruit.melon]: "A refreshing fruit"
};
There's no way to create an object with already predefined fields. So, we can create it only by iterating. And here there're 2 unpleasant things:
-
the 1st one as it's iterating creation we always start with an empty object and that means we have
Partial
type in the end. -
the 2d unpleasant thing here is that js doesn't allow us to
pass which keys will be returned fromObject.keys
method, it always
expects to returnstring
keys.function makeFruitToNumberMap() { const keys = Object.keys(Fruit); // 2d issue: keys type is string[] const map = keys.reduce((acc, key) => ({ ...acc, [key]: 0 }), {}); // 1st issue: original object is empty and we set values iteratively return map; }
So, to resolve it we can use casting
function makeFruitToNumberMap() {
const keys = Object.keys(Fruit) as TFruit[];
const map = keys.reduce((acc, key) => ({ ...acc, [key]: 0 }), {});
return map as FruitMapToNumber;
}
答案2
得分: 0
Anastasiya Stanevich 让我意识到我所缺少的是类型断言,即使用 … as Type
来断言我的对象确实是所需的类型 Type
。
我还应该使用 Partial<Type>
来为我的“尚未完成”的对象定义类型。
通常,在我提供的示例中:
function makeFruitToNumberMap(): FruitMapToNumber {
const map: Partial<FruitMapToNumber> = {}; // 👈 部分
let fruit: Fruit;
for (fruit in FRUIT_DESCRIPTIONS) {
map[fruit] = 0;
}
return map as FruitMapToNumber; // 👈 类型断言
}
英文:
Anastasiya Stanevich made me realize that what I was lacking is type assertion, i.e., using … as Type
to assert that my object was indeed, at this point, of the required type Type
.
I should also be using Partial<Type>
to type my “not yet completed” object.
Typically, in the sample case I provided:
function makeFruitToNumberMap(): FruitMapToNumber {
const map: Partial<FruitMapToNumber> = {}; // 👈 Partial
let fruit: Fruit;
for ( fruit in FRUIT_DESCRIPTIONS ) {
map[ fruit ] = 0;
}
return map as FruitMapToNumber; // 👈 Type assertion
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论