Issue
Let's say I have the following enum and type declared in my code:
enum MyEnum {
FIRST,
SECOND
};
type MyType = {
firstKey: string | null,
secondKey: boolean,
thirdKey: MyEnum
}
Then, using the type, I declare a variable that looks like this
let globalObject: MyType = {
firstKey: "String",
secondKey: false,
thirdKey: MyEnum.FIRST
}
Based on the answer from another question, I have created the following generic type:
type MainFormDataField<T extends MyType> = {
[K in keyof T]: {
sKey: K;
vValue: T[K];
};
}[keyof T];
I want to use it in a function that assigns a value to the specific key of the previously created object. I don't want to modify the object directly. The reason for this is that I need to execute some logic before and after each change of any value in the object. When I pass an array of objects into the function, everything works as expected. I have an array with multiple objects and type of value of each object is specific for the selected key - it does not combine type of all values in the array. However, in the function itself it does not work this way. The type of the value property is a union of all possible types for each key.
function setSomeValue(elements?: MyFieldType<MyType>[]) {
if(!elements) {
// Some alternative logic
return;
}
// Some logic required before the value changes
for (const element of elements) {
globalObject[element.key] = element.value; // Type 'string | boolean | MyEnum | null' is not assignable to type 'never'. Type 'null' is not assignable to type 'never'.
}
// Some logic required after the value changes
}
setSomeValue([{key: "secondKey", value: false}, {key:"thirdKey", value: false}]) // Type 'false' is not assignable to type 'MyEnum'.
How can I solve this?
Solution
The compiler is unable to handle the "correlated union types" described in ms/TS#30581, however, there is a suggested refactor described in ms/TS#47109, which considers moving to generics. We will need to add another function responsible for setting a single field. This function will accept generic parameters constrained to be a key of MyType
and by using the type mentioned below we will accept the element
argument:
type SetArgs<T extends keyof MyType> = {
[K in T]: {
key: K;
value: MyType[K];
};
}[T];
const set = <T extends keyof MyType>(obj: MyType, element: SetArgs<T>) => {
obj[element.key] = element.value;
};
By using the generics we prevent the compiler from narrowing the type. For example, in the set
function the element.key
is no longer showing exact values of it in union, but rather generic version, which is what we are looking for.
Usage:
function setSomeValue<T extends MyFieldType<MyType>>(elements?: T[]) {
if (!elements) {
return;
}
for (const element of elements) {
set(globalObject, element); // no error
}
}
Looks good!
Answered By - wonderflame
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.