Suppose you have an array of files.
const files = ["file1.ts", "file2.ts", "file3.ts"];
Each file should also have a corresponding test file.
const testfiles = {
"file1.ts": "file1.test.ts",
"file2.ts": "file2.test.ts",
};
But file3.ts
doesn't have a matching test file. How would you catch this?
This is a perfect use case for the Record type. The Record utility type takes two type parameters, Keys
and Type
.
Record<Keys, Type>;
It creates an object type whose keys are all the Keys
. This means, if we give it a union as the first parameter, it will create an object whose keys are all the types in that union!
/* type Bar = {
a: string;
b: string;
} */
type Bar = Record<"a" | "b", string>;
Note: this is different from index signatures. An index signature looks like this:
type Foo = {
[key: string]: string;
};
Index signatures tell you the general shape of an object. With an index signature, the object can have as many keys and values as you want, as long as they match the index signature.
// okay
const a: Foo = {};
// also okay
const b: Foo = { foo: "bar" };
// error, Type 'number' is not assignable to type 'string'.ts(2322)
const c: Foo = { foo: 3 };
But record types create an object with the exact keys you have specified.
If you pass in general types as keys (like string
or number
), the record type will create a type with an index signature for you:
/* type Baz = {
[x: string]: string;
} */
type Baz = Record<string, string>;
/* type Foo = {
[x: string]: string;
[x: number]: string;
} */
type Foo = Record<string | number, string>;
But, if you pass in a union of literal types, the record type will create an object where the keys are all the literal types from your union.
Now all we need to do is turn our array into a union type.
type FileName = (typeof files)[number];
type TestFiles = Record<FileName, string>;
And now we get an error.
const files = ["file1.ts", "file2.ts", "file3.ts"] as const;
type FileName = (typeof files)[number];
type TestFiles = Record<FileName, string>;
// Property '"file3.ts"' is missing in type '{ "file1.ts": string; "file2.ts": string; }' but required in type 'TestFiles'.ts(2741)
const testfiles: TestFiles = {
"file1.ts": "file1.test.ts",
"file2.ts": "file2.test.ts",
};