Reputation: 23
Here is my "normal" typing test:
const prop1 = 'property1';
const prop2 = 'property2';
type Type1 = Record<typeof prop1, string> & Record<typeof prop2, number>;
let obj1: Type1;
console.log(obj1.property1 === '1') // ok
console.log(obj1.property2 === 2) // ok
console.log(obj1.property1 === 1) // error : This condition will always return 'false' since the types 'number' and 'string' have no overlap.
console.log(obj1.property2 === '2') // error : This condition will always return 'false' since the types 'number' and 'string' have no overlap.
Here is my "dynamic" typing test; the only difference is the way I give property names:
const prop1_ = prop1 + '_';
const prop2_ = prop2 + '_';
type Type2 = Record<typeof prop1_, string> & Record<typeof prop2_, number>;
let obj2: Type2;
console.log(obj2.property1_ === '1') // no error : ok
console.log(obj2.property2_ === 2) // no error : ok
console.log(obj2.property1_ === 1) // !!! missing error !!!
console.log(obj2.property2_ === '2') // !!! missing error !!!
Is there a way to make typescript manage my dynamically named property?
Upvotes: 1
Views: 59
Reputation: 327624
Template literal types enable the compiler to keep track of string literal type concatenation, but it doesn't happen when you concatenate actual string literals using the +
operator:
const ab = "a" + "b"
// const ab: string 😢
So your prop1_
and prop2_
values are just of type string
, and so Type2
was just something like Record<string, string> & Record<string, number>
which has no idea what your actual keys are.
There is a feature request at microsoft/TypeScript#44905 asking for such support so that "a" + "b"
would be of type "ab"
. But who knows when or even if such a thing will be implemented.
For now, if you want to be able to get anything like this, you'll need to use an actual template literal string to get template literal types, and furthermore you will need to hint the compiler that you care about the literal type via a const
assertion:
const prop1_ = `${prop1}_` as const;
// const prop1_: "property1_"
const prop2_ = `${prop2}_` as const;
// const prop2_: "property2_"
There was an effort to make it so that you didn't need as const
, but it broke too many things so they abandoned it (see ms/TS#42588). Anyway, with that change, your prop1_
and prop2_
values now have literal types and the rest of your code works as expected.
Alternatively you could make a helper function which uses the +
operator at runtime but whose call signature returns a template literal type concatenating the input types:
const concat = <T extends string, U extends string>(t: T, u: U) => (t + u) as `${T}${U}`
and it behaves similarly:
const prop1_ = concat(prop1, "_");
// const prop1_: "property1_"
const prop2_ = concat(prop2, "_");
// const prop2_: "property2_"
I'm not sure it's worth it to do that instead of template literal strings directly though.
Upvotes: 1