'Typescript: Having problems with mapping nested Record values wrapped in vue3 reactive proxy
UPDATED VERSION:
I have several implementations of a generic class, which is wrapped in the vue3 composition api function "reactive", which is basically a proxy which keeps track of any updates:
export declare function reactive<T extends object>(target: T): UnwrapNestedRefs<T>;
How can I implement a class so that the constructor takes an object of such implementations, and have such inner types unwrapped as parameter of a callback function which is the second argument of the constructor?
Like this:
const campaignWrapped = reactive(new MyWrapper<Campaign>(campaign));
const batchesWrapped = reactive(new MyWrapper<Batch[]>(batches));
new MyClass({ first: campaignWrapped, second: batchesWrapped }, (states) => {
states.first; // Should be of type Campaign
states.second; // Should be of type Batch[]
});
It seems like it's not possible to derive the type from the proxy object, because extends MyClass<infer U> ? ... doesn't match anymore. The only way around seems to be adding an interface to the Class and check for this interface instead. Even if I unwrap UnwrapNestedRefs first and infer it's type, I still can't get the real inner type.
OLD VERSION: (simplified)
(just for the context, as my wrong attempt might help someone to understand the difference between union and intersect - which wasn't clear to me)
I want to get the unboxed types in a callback, which have to be derived from generic types.
So far so good. It seems to work if I have only one record entry.
But as soon as there is another, it fails.
The resulting type looks ALMOST correct:
Record<"first", Campaign> | Record<"second", Batch[]>
How can I tell typescript to use intersect and not union? So the type would become:
Record<"first", Campaign> & Record<"second", Batch[]>
(BONUS QUESTION: Is it possible to get rid of the LSW interface and use the type of the class directly? For me the extends LazyStateWrapper<infer U> ? ... didn't work.)
Working example in typescriptlang.org playground here
And here is the code inline:
type UnwrapInner<X, S extends string> = X extends LSW<infer X>
? Record<S, X>
: never;
type UnwrapLazyStateWrapper<T> = T extends Record<infer S, infer U> ? (S extends string ? UnwrapInner<T[S], S> : never) : never;
export class TransientLazyState<X, T extends object> {
constructor(states: X, cb: (states: UnwrapLazyStateWrapper<X>) => T | undefined) {
console.log('todo');
}
}
interface LSW<T extends object> {
getStateClone(): T | undefined;
}
export class LazyStateWrapper<T extends object> implements LSW<T> {
private obj: T;
constructor(instance: T) {
this.obj = instance;
}
getStateClone(): T | undefined {
return {} as T;
}
}
type Campaign = { id: number; foo: string };
type Batch = { id: number; bar: string };
const campaign: Campaign = { id: 123, foo: 'x' };
const batches: Batch[] = [];
const campaignWrapped = new LazyStateWrapper(campaign);
const batchesWrapped = new LazyStateWrapper(batches);
const test1 = new TransientLazyState({ first: campaignWrapped }, (states) => {
const xy = states.first; // Yay. Works! Is of type "Campaign"
console.log(xy);
return undefined;
});
const test2 = new TransientLazyState({ first: campaignWrapped, second: batchesWrapped }, (states) => {
const xy = states.first; // Doesn't work anymore, once there are more records. But to me the type looks ALMOST correct. ERROR: Property 'first' does not exist on type 'Record<"first", Campaign> | Record<"second", Batch[]>'. Property 'first' does not exist on type 'Record<"second", Batch[]>'.(2339)
console.log(xy);
return undefined;
});
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
