如何使用Typescript拾取和重命名某些键?

t98cgbkg  于 2023-03-31  发布在  TypeScript
关注(0)|答案(4)|浏览(166)

我有一个接口User

interface User {
    _id     : string;
    name    : string;
    email   : string;
    password: string;
    phone   : number;
}

我有另一个接口UpdatedBy

interface UpdatedUser {
    id  : string;
    name: string;
}

我知道我可以使用Pick,但我想在UpdatedUser接口中将_id重命名为id

type UpdatedUser = Pick<User, '_id' | 'name'>; // How can I turn _id into id?

更新:我基本上想做一个更干净的版本:

export interface UpdatedUser extends Pick<User, 'name'> {
    id  : Extract<User, '_id'>;
}
lb3vh1jj

lb3vh1jj1#

对于重命名的Pick,没有内置类型,幸运的是,我们可以通过合理的努力创建一个。

简单变体

type IdRenamed = Omit<User, "_id"> & { id: User["_id"] }
// { name: string; email: string; password: string; phone: number; id: string;}

Playground

单属性动态版本

type PickRename<T, K extends keyof T, R extends PropertyKey> =
    Omit<T, K> & { [P in R]: T[K] }

type T21 = PickRename<User, "_id", "id"> // same type as above
type T22 = PickRename<User, "foo", "id"> // error, foo is no property

Playground
TS 4.1备选方案:使用mapped type as clauses。它的优点是保留了readonly或可选的(?)属性修饰符(更多细节请参见同态Map类型12)。

type PickRename<T, K extends keyof T, R extends PropertyKey> = {
    [P in keyof T as P extends K ? R : P]: T[P]
} // type instantiation same as previous example

Playground

多属性动态版本

type PickRenameMulti<T, R extends
    { [K in keyof R]: K extends keyof T ? PropertyKey : "Error: key not in T" }
    > = Omit<T, keyof R> & UnionToIntersection<
        { [P in keyof R & keyof T]: { [PP in R[P]]: T[P] } }[keyof R & keyof T]
    >

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends
    ((k: infer I) => void) ? I : never

type T31 = PickRenameMulti<User, { _id: "id"; name: "firstName" }>
type T32 = PickRenameMulti<User, { foo: "id" }> // error, foo is no property

注意:有关帮助器的更多细节,请参阅UnionToIntersection类型。
Playground
TS 4.1再次简化了语法并产生同态Map类型:

type PickRenameMulti<T, R extends
    { [K in keyof R]: K extends keyof T ? PropertyKey : "Error: key not in T" }
    > = { [P in keyof T as P extends keyof R ? R[P] : P]: T[P] }

Playground

TS 4.1:从所有属性键中删除_前缀

type DropUnderscore<T> = {
    [K in keyof T as K extends `_${infer I }` ? I : K]: T[K]
};
type T4 = DropUnderscore<User> // "_id" and "_email" renamed to "id", "email"

Playground

kmb7vmvb

kmb7vmvb2#

一个 * 稍微 * 干净的版本将是...

export interface UpdatedUser extends Pick<User, 'name'> {
  id: User['_id'];
}

...但我不确定如何按您的建议在运行时重命名它。这是一个有趣的用例。

v9tzhpje

v9tzhpje3#

ford04's answer的问题是它没有保留重命名键的optional/required属性。下面是如何在考虑键是可选还是必需的情况下重命名一个 prop :

type KeyRenamed<T, K extends keyof T, R extends PropertyKey> = Omit<
  T,
  K
> &
  (undefined extends T[K] ? { [P in R]?: T[K] } : { [P in R]: T[K] });
type NameOptional = {
    name?: string;
}

type NameRequired = {
    name: string;
}

type DisplayNameRequired = KeyRenamed<NameRequired, 'name', 'displayName'>
type DisplayNameOptional = KeyRenamed<NameOptional, 'name', 'displayName'>

const displayNameOptional: DisplayNameOptional = { displayName: 'Ali' } 
const displayNameOptional_missing: DisplayNameOptional = {  } // no error, since displayName is kept optional after rename
const displayNameRequired: DisplayNameRequired = { displayName: 'Ali' } 
const displayNameRequired_missing: DisplayNameRequired = {  } // error, since displayName is kept required after rename

你可以将同样的事情应用到答案中那些更高级的类型,例如,如果你想一次重命名多个键。
Playground

nfg76nw0

nfg76nw04#

我真的很喜欢ford04's多属性的动态版本,但我希望可选键保持可选,就像在Alireza Mirian's answer中一样。
所以我在这里把两者结合起来。

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I,
) => void
    ? I
    : never;

export type RenameProperties<
    T,
    R extends {
        [K in keyof R]: K extends keyof T ? PropertyKey : "Error: key not in T";
    },
> = Omit<T, keyof R> &
    UnionToIntersection<
        {
            [P in keyof R & keyof T]: undefined extends T[P]
                ? { [PP in R[P]]?: T[P] }
                : { [PP in R[P]]: T[P] };
        }[keyof R & keyof T]
    >;

相关问题