I have the next situation:
const data: Record<string, string> = {
a: '110px',
b: '160px',
};
interface Props {
d?: keyof typeof data[];
}
const t = (d: Props) => 'hi' d;
t(['a']) // here should be allowed only data keys (a and b)
I want to restrict the t()
arguments only to data
object keys. I tried as above but i get TS error: Type 'string[]' has no properties in common with type 'Props'.(2559)
. How to achieve that?
NOTE: i need to do this dynamycly reading the keys of the object.
demo: link
CodePudding user response:
Two answers for you:
If by "dynamically" you mean at runtime, no, that's not possible with TypeScript. TypeScript only works with compile-time information.
If by "dynamically" you mean it works even if you change the object literal that creates
data
, you can do it with(keyof typeof data)[]
and allowing TypeScript to infer the type ofdata
(by removing the type annotation on it):const data = { // <== Note no `Record<string, string>` on this a: '110px', b: '160px', }; const t = (d: (keyof typeof data)[]) => 'hi' d; t(["a", "b"]); // <=== Works as expected t(["x"]); // <=== Error as desired
If you change the object literal defining
data
so that it (now) has ac
property, the definition oft
doesn't have to change (playground), it picks that up.As captain-yossarian from Ukraine pointed out in a comment, starting with TypeScript 4.9, if you want to make sure that you can't accidentally add an entry to
data
that doesn't have a string value (or even a more specific one, more in a minute), you can use the newsatisfies
operator. That lets you enforce a limitation on the object's properties without broadening its type, like this:const data = { a: '110px', b: '160px', } satisfies Record<string, string>; //^^^^^^^^^−−−−−− new TypeScript 4.9 operator const t = (d: (keyof typeof data)[]) => 'hi' d; t(["a", "b"]); // <=== Works as expected t(["x"]); // <=== Error as desired
When you do that, if you try to add
c: 42
todata
, you'll get an error from TypeScript (link) because that would mean thatdata
's type didn't satisfy the constraintRecord<string, string>
. But unlikeas
, it won't narrow the type ofdata
, so ourkeyof typeof data
still sees onlydata
's actual property names as a union ("a" | "b"
), not juststring
.As he also pointed out, if you want to make sure all of your values in
data
are in the form<number>px
, you can use a template literal type likeRecord<string, `${number}px`>
withsatisfies
(link).