#Why does this template literal type need to add `& string` to `keyof Type` to compile?

37 messages · Page 1 of 1 (latest)

cunning night
#

I think it will be easier to show the code:

steep daggerBOT
#
Bawdy Ink Slinger#9429

Preview:```ts
type PropEventSource<Type> = {
on(eventName: ${string & keyof Type}Changed, callback: (newValue: any) => void): void; // !!! Why can't this just be ${keyof Type}Changed ?
};

/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
...```

vapid tinsel
#

Because "symbols" can be keys inside an object and they don't have a string representation.

vapid tinsel
#

Doing & string will discard everything that isn't a string because intersecting incompatible types "returns" never

cunning night
vapid tinsel
#

If you have:
type X = symbol | true | 10 | 'hello'; and then you do type Y = X & string; then the resulting type will be evaluated to something like: symbol & string | true & string | 10 & string | 'hello' & string which then becomes never | never | never | 'hello which is finally simplified to 'hello' because nevers are discarded in a union.

cunning night
vapid tinsel
#

The "keys", not the values

cunning night
#

oh

#

right!

vapid tinsel
#

const someSymbol = Symbol(); const unwatchedPerson = { firstName: 'Foo', [someSymbol]: 10 }

cunning night
#

As you can tell, I don't know how to use symbols yet, hah

#

Sorry

vapid tinsel
#

You're creating a "string" key called "sym" at line 15

#

You need the [], like [sym]: '5'

cunning night
#

Wow, if you hadn't spoonfed me this information, I would just end up asking why I can't person.on("symChanged"...

#

Is there any way I could have figured out my original question without someone telling me? I can't figure out a way to see what keyof Type might be, so I felt like I needed to ask this question if I didn't already know.

vapid tinsel
#

You would probably have Googled a little bit and found out that you can use "Extract" instead of the intersection

cunning night
#

Actually... Maybe I'm just wrong about an assumption: are object keys any?

vapid tinsel
#

Object keys are string | number | symbol

#

There's an built-in type called PropertyKey that is a union of those

cunning night
cunning night
vapid tinsel
#

Intersections, beside for the simplest case of "merging object types"... could be considered advanced TS

#

The other way to do it: ```typescript
type PropEventSource<Type> = {
on(eventName: ${Extract<keyof Type, string>}Changed, callback: (newValue: any) => void): void;
};

cunning night
vapid tinsel
#

Another way would be to do the opposite (remove symbols) ```typescript
type PropEventSource<Type> = {
on(eventName: ${Exclude<keyof Type, symbol>}Changed, callback: (newValue: any) => void): void;
};

#

I personally learned most of the advanced TS from reading types from the experts on the #ts-discussion channels and some blogs

cunning night
vapid tinsel
#

It looks like a lot of TS tutorials assume some level of JS proficiency... and it's known that object keys in JS can only be of those type. I cannot really answer your question properly.

cunning night
#

Okay, thanks for the help! I'm wondering if this might be a VS code feature request; I think I should be able to hover over keyof in keyof Type and see a popup that says either string | number | symbol or PropertyKey. Am I missing something?

#

Maybe I should ask in #ts-discussion ?

vapid tinsel
#

Yeah, sure. I would just like to add that only string | number | bigint | boolean | null | undefined can be used in string interpolation ${X}. For example, in a case when you would have type SomeUnion = 10 | 'hello' | { foo: 3 } then it would also not work because an object cannot be used in this context.

#

(Objects cannot be object keys so a union like that will never happen from using keyof... )

cunning night
#

That makes sense 🙂 Thanks