问题
The problem is that I want to use a new type as key for a dictionary but I want to dynamically generate the entries using a loop. But I will prefer not to use ? operator because I know I'll fill all keys and I don't want to force ! evaluation for each call.
const studentList = [
A,
B,
C,
// many many students
] as const;
type Students = typeof studentList[number];
// class Info(); // some properties like grades and teacher
const studentInfo: { [key in Students]: Info }; // won't work const must be initialized
const studentInfo: { [key in Students]: Info } = {}; // won't work as all keys are not defined
const studentInfo: { [key in Students]?: Info } = {}; // I want to avoid ?
for (let name of Students) {
// get info from some db
{ grade, teacher } = lookUp(name)
// initialize dictionary keys
studentInfo[name] = Info(grade, teacher);
}
studentInfo.A!.name; // I want to avoid !
Is there anyway to use the key itself to generate the dictionary so that the type signatures checkout. Maybe something like const studentInfo = { key for key in Students | .... };
. And the reason I feel this should work out is because the compiler can reason that all the keys of the type have been used to create the dictionary.
Is this possible in some way?
I previously asked a version of this question with enums, but thanks to this answer I realized a new type might be better and hit this new question :).
回答1:
This is the best I can come up with at the moment:
const students = ['A', 'B', 'C'] as const;
class Info {
name = '';
}
type StudentInfo = { [key in typeof students[number]]: Info; };
const studentInfo = students.reduce((a, v) => ({ ...a, [v]: new Info() }), {})
as StudentInfo;
studentInfo.A.name;
The new Info()
part is where you would do your database lookup and create the actual Info
object.
Ideally, there should be a way to avoid that cast when creating the studentInfo
object as well, but again the problem here is bridging runtime objects to compile-time constructs.
The compile time StudentInfo
type is correctly defined, but TypeScript is unable to infer that the result of my reduce()
operation conforms to that type. I thought I might have better luck using the ES2019 Object.fromEntries()
method, but the compiler still needs the explicit cast:
const studentInfo = Object.fromEntries(students.map(s => [s, new Info()]))
as StudentInfo;
Update
I asked a new question to see if there was a way to get around having to do an explicit type assertion and got some good answers. The cast can be avoided by using Object.create(null)
instead of {}
to initalize the result of the reduce()
operation:
const studentInfo = students.reduce((a, v) => ({ ...a, [v]: new Info() }),
Object.create(null));
There are however some caveats. Check the linked question for details.
来源:https://stackoverflow.com/questions/65439261/generate-typed-dictionary-with-a-for-loop-using-a-new-type-as-key-but-without-us