5

TLDR: In my generic function, i want to have myFunction(['width', 'left']) to return the type {width: string, left: string}.

Long version:

I have an Typescript function which has an string array as an input and returns an object with keys of the array as a value:

export interface Dictionary<T> {
  [index: string]: T | undefined;
}
var getStyle = function (  
  element: Element,
  propertyNames: readonly string[]
) { 
  let gCS= window.getComputedStyle(element)
  let result: Dictionary<string> = {};
  propertyNames.forEach((prop)=>{
    result[prop]=gCS.getPropertyValue(prop);
  });
  return result;
};    

The typescript return value is a Object/Dictionary, but without specific properties.

var resultObj = getStyle(document.body, ['width']);
resultObj.width; // should be ok
resultObj.height; // should be not ok

I tried many things. The best thing was that one:

export type RestrictedDictionary1<T, P extends readonly string[]> = {
    [index in keyof P]?: T | undefined
}
declare function getStyle1<P extends readonly string[]>(  
    element: Element,
    propertyNames: P
): RestrictedDictionary1<string, P>;

var resultObj1 = getStyle1(document.body, ['width']);  // Huh? Why an array
resultObj1.width; // should be ok, but both are unvalid for TS
resultObj1.height; // should be not ok, but both are unvalid for TS

Typescript now got an array from that. I have no idea why.

The last try is again an interface, but the [index in P] part does not work

export interface RestrictedDictionary2<T, P extends string[]> {
    [index in P]: T | undefined;  // A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
}
declare function getStyle2<P extends string[]>(  
    element: Element,
    propertyNames: P
): RestrictedDictionary2<string, P>;

var resultObj2 = getStyle2(document.body, ['width']);
resultObj2.width; // should be ok
resultObj2.height; // should be not ok

1 Answer 1

5

You are pretty close in your thinking the syntax is a bit off though. If P is string[] then the keyof P are just the members of array, not the values. You could use P[number] to get at the types of the values in the array. Also interfaces can't contain mapped types (the [index in P]: ... syntax), only type aliases can (type definitions). Also there is a predefined mapped type called Record that is exactly what you need no need to define a new one


var getStyle = function<T extends string> (  
  element: Element,
  propertyNames: readonly T[]
): Record<T, string> { 
  let gCS= window.getComputedStyle(element)
  let result = {} as Record<T, string>;
  propertyNames.forEach((prop)=>{
    result[prop]=gCS.getPropertyValue(prop);
  });
  return result;
};   
var resultObj = getStyle(document.body, ['width']);
resultObj.width; // should be ok
resultObj.height; // err

Play

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.