我想要一个返回对象的函数。所述函数还可以将 n 个函数作为参数,并在返回之前使用它们来修改对象。我的问题是,在进行更改后,我没有得到正确的打字稿类型。
在本例中,我有主函数 getUser
和两个修饰函数 modStatus
、modAge
。当我使用两个修饰符调用 getUser
时,我希望将 UserModStatus & UserModAge & User
作为返回类型
type User = {
name: string;
};
type UserModAge = {
age: number;
};
type UserModStatus = {
status: "single" | "married";
};
type Mod<EXTRA> = <BASE>(user: BASE) => EXTRA & BASE;
const getUser = <T>(...mods: Mod<T>[]) => {
const user: User = {
name: "John",
};
return mods.reduce((acc, mod) => mod(acc), user);
};
const modAge: Mod<UserModAge> = (user) => {
return { ...user, age: 21 };
};
const modStatus: Mod<UserModStatus> = (user) => {
return { ...user, status: "married" };
};
const res = getUser(modStatus, modAge); // const res: User
然而,返回类型只是初始的“用户”类型。
如果我使用静态参数它工作正常
const getUser = <T, V>(mod1: Mod<T>, mod2: Mod<V>) => {
const user: User = {
name: "John",
};
return mod1(mod2(user));
};
const res = getUser(modStatus, modAge); // const res: UserModStatus & UserModAge & User
但我需要它们是动态的。有没有办法做到这一点?
期望的结果是在应用不同的修饰符时得到正确的类型
const res = getUser();
// const res: User
const res = getUser(modAge);
// const res: UserModAge & User
const res = getUser(modStatus);
// const res: UserModStatus & User
const res = getUser(modStatus, modAge);
// const res: UserModStatus & UserModAge & User
答案 0 :(得分:1)
是的,你可以做到这一点,但它需要一些高级类型(或者更确切地说,我需要它们)。我只会展示我添加的内容,但可以在 playground:
中查看完整的示例// nothing new added to the next line, but im referring often to Mod and the EXTRA generic.
type Mod<EXTRA> = <BASE>(user: BASE) => EXTRA & BASE;
// Converts Mod<any>[] into EXTRA1 | EXTRA2 | EXTRA3...
type UnionOfReturnTypes<U extends Mod<any>[]> = ReturnType<U[number]>
// Converts EXTRA1 | EXTRA2 | EXTRA3 into EXTRA1 & EXTRA2 & EXTRA3
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
// This takes a BASE (User in our case) and intersects it with EXTRA1 & EXTRA2 & EXTRA3.
// It also combines the types UnionOfReturnTypes and UnionToIntersection
type ModifiedResult<BASE, MODS extends Mod<any>[]> = BASE & UnionToIntersection<UnionOfReturnTypes<MODS>>;
const getUser = <T extends Mod<any>[]>(...mods: T) => {
const user: User = {
name: "John",
};
return mods.reduce((acc, mod) => mod(acc), user) as ModifiedResult<User, T>;
};
const res = getUser(modStatus, modAge); // const res: User
// No compiler errors and intellisense
res.age;
res.name;
res.status;
说明:
UnionOfReturnTypes
:此类型使用 ReturnType 和 indexing 组合来自 Mod
函数的所有返回类型。但它只能提取 EXTRA
类型,因为 BASE
信息只有在实际传递参数时才可用,而这不能在类型中发生。因此,结果是 EXTRA1 | EXTRA2 | EXTRA3
等等,对于每个使用过的 Mod
。
UnionToIntersection
这个代码来自一个 answer of jcalz,我们需要它来将我们的并集转换成一个交集。 UnionOfReturnTypes
返回的联合对我们没有多大帮助,因为我们想要的 user
结果不是:
type finalUser = {name: string} | {age: number} | {status: "married" | "single"}
但结果如下:
type finalUser = {
name: string,
age: number,
status: "married" | "single"
};
这对我们来说确实是 UnionToIntersection
,它转换 EXTRA1 | EXTRA2
-> EXTRA1 & EXTRA2
ModifiedResult
这是结合了 UnionToIntersection
和 UnionOfReturnTypes
的类型,并且还添加了我们无法从 UnionOfReturnTypes
中提取的基数。这最终确定了解决方案,现在让我们动态地将 mod 添加到 getUser()
并返回完整类型。