Reputation: 3065
With the following abstraction, I am able to call this method and the return type is based on the string based fields I pass into it shown below.
abstract class Entity {
abstract id: string
}
class User extends Entity {
constructor(obj: Partial<User>) {
super()
Object.assign(this, obj)
}
id: string
name: string
age: number
}
class Post extends Entity {
constructor(obj: Partial<Post>) {
super()
Object.assign(this, obj)
}
id: string
content: string
time: Date
}
export const useSyncFields = <T extends Entity, U extends (keyof T & string)[]>(
type: { new(obj: T): T },
id: string,
fields: U,
): { [K in U[number]]: T[K] } => {
...
}
const user1: {id: string, name: string} = useSyncFields(User, "1234", ["id", "name"])
const user2: {id: string, age: number} = useSyncFields(User, "1234", ["id", "age"])
const post: {content: string} = useSyncField(Post, "4567", ["content"]
// etc
By using the type params <T extends Entity, U extends (keyof T & string)[]
and the return type { [K in U[number]]: T[K] }
, the inferred return type from Typescript "correct", but neither VSCode or WebStorm do an awesome job inferring the type correctly.
I feel like there's a better set of type params / return types that would be better inferred. Is what I have really the best I could be doing here? Or is there some type ninja out there who can guide me to a better solution?
Upvotes: 1
Views: 1641
Reputation: 187272
If you simply want to make this simpler, I'd go with:
export const useSyncFields = <T extends Entity, U extends keyof T>(
type: { new(obj: T): T },
id: string,
fields: U[],
): Pick<T, U> => {
return {} as any // implementation
}
If you don't care about the length or order of arrays in your type then you don't need be generic over the whole array. So this is only generic over the members of the fields
array. This means you don't have to go fishing with U[number]
to get the union of keys.
It also uses the Pick
utility type which saves you from having the complex looking mapped return type.
Upvotes: 2