I have an enum and its associated union type:
type User = { name: string, age: number }
export enum StorageTypeNames {
User = "user",
Users = "referenceInfo",
IsVisibleSearchPanel = "searchPanel",
PropertyPanelInfo = 'propertyPanelInfo'
};
type StorageType =
| { name: StorageTypeNames.User, data: User }
| { name: StorageTypeNames.Users, data?: User[] }
| { name: StorageTypeNames.IsVisibleSearchPanel, data?: boolean }
| { name: StorageTypeNames.PropertyPanelInfo, data: {visibility: boolean};
and there is a class using these types in the method of which I am trying to implement a generic function
export class StorageHelper {
private static readonly _storage = window.localStorage;
static get<T>(type: StorageType): T;
static get<T>(type: StorageTypeNames): Pick<StorageType, 'data'>;
static get<T>(type: StorageType | StorageTypeNames): T {
let data: string | null = null;
if(typeof type === 'string')
data = this._storage.getItem(type);
else
data = this._storage.getItem(type.name);
return data ? JSON.parse(data) : null;
}
}
as a result I want to get this: depending on the incoming argument, the typescript suggested the properties of the outgoing object:
const userData = StorageHelper.get(StorageTypeNames.User).data. //typeScript dosent help
please tell me how to do it, thanks in advance
The call signatures of get()
don't express what you're doing. It looks like you want to accept a parameter either of some subtype of StorageType
, in which case you return a value of the same subtype; or of some subtype of StorageTypeNames
, in which case you return a value of the corresponding member of StorageType
.
For the first case, you need get()
to be generic in the type T
of type
, where T
is constrained to StorageType
. And then the return type is also T
:
static get<T extends StorageType>(type: T): T;
Your mistake was by making type
of type StorageType
instead of type T
constrained to StorageType
, since then the compiler has no idea what T
is.
For the second case, you want get()
to be generic in the type K
of type
, where K
is now constrained to StorageTypeNames
. But now you want to take the StorageType
union and filter it to get just the one member whose name
property is of type K
. We can use the Extract<T, U>
utility type to filter unions this way. Here's the call signature:
static get<K extends StorageTypeNames>(type: K): Extract<StorageType, { name: K }>;
And then you can implement the method however you want:
static get(type: StorageType | StorageTypeNames) {
/* impl */
}
(although if your method actually has a chance of returning null
you should make the output type include the null
type like T | null
or Extract<StorageType, {name: K}> | null
so that someone using the widely-recommended --strictNullChecks
compiler option would benefit from the extra checking.)
Let's test it out:
StorageHelper.get(StorageTypeNames.User).data.age.toFixed(2); // okay
StorageHelper.get(
StorageHelper.get(StorageTypeNames.PropertyPanelInfo)
).data.visibility // okay
Looks good. The compiler understands that, for example, get(StorageTypeNames.User)
returns a value of type { name: StorageTypeNames.User, data: User }
, which is what you wanted.