The problem is a bit wider than OP's question.
For example, let's define an interface and variable of the interface
interface IObj {
prop: string;
}
const obj: IObj = { prop: 'string' };
Can we assign obj
to type Record<string, string>
?
The answer is No. Demo
// TS2322: Type 'IObj' is not assignable to type 'Record<string, string>'. Index signature for type 'string' is missing in type 'IObj'.
const record: Record<string, string> = obj;
Why this is happening?
To describe it let's refresh our understanding of "upcasting" and "downcasting" terms, and what is the meaning of "L" letter in SOLID principles.
The following examples work without errors because we assign the "wider" type to the more strict type.
Demo
const initialObj = {
title: 'title',
value: 42,
};
interface IObj {
title: string;
}
const obj: IObj = initialObj; // No error here
obj.title;
obj.value; // Property 'value' does not exist on type 'IObj'.(2339)
IObj
requires only one prop so the assignment is correct.
The same works for Type. Demo
const initialObj = {
title: 'title',
value: 42,
};
type TObj = {
title: string;
}
const obj: TObj = initialObj; // No error here
obj.title;
obj.value; // Property 'value' does not exist on type 'TObj'.(2339)
The last two examples work without errors because of "upcasting". It means that we cast a value type to the "upper" type, to the entity type which can be an ancestor. In other words, we can assign Dog to Animal but can not assign Animal to Dog (See meaning of "L" letter in SOLID principles). Assigning the Dog to the Animal is "upcasting" and this is safe operation.
Record<string, string>
is much wider than the object with just one property. It can have any other properties.
const fn = (record: Record<string, string>) => {
record.value1;
record.value2;
record.value3; // No errors here
}
That's why when you assign the IObj
Interface to Record<string, string>
you get an Error. You assign it to the type that extends IObj
. Record<string, string>
type can be a descendant of IObj
.
In other answers, it is mentioned that using of Type can fix the problem. But I believe it is wrong behavior and we should avoid of using it.
Example:
type TObj = {
title: string;
}
const obj: TObj = {
title: 'title',
};
const fn = (record: Record<string, string>) => {
record.value1;
record.value2;
// No errors here because according to types any string property is correct
// UPD:
// FYI: TS has a flag `noUncheckedIndexedAccess` which changes this behavior so every prop becomes optional
record.value3;
}
fn(obj); // No error here but it has to be here because of downcasting
The last example with the comparison of Type and Interface.
P.S.
Take a look at this issue with related question, and interesting comment.
type
rather thaninterface
in that case, because interfaces require by design explicit index signatures in those cases. – Paleobiology