在 Typescript 中递归修改给定类型/接口的通用方法

Ada*_*m D 5 typescript typescript-generics typescript2.0

我正在努力制作一个泛型,它将递归地修改嵌套递归数据结构中找到的所有元素。这是我的数据结构的示例。使用此递归数据定义,任何帖子都可以有无限数量的评论和回复。

type Post = {
    user: string,
    content: string,
    comments: Comment[],
    reactions: Reaction[],
}

type Comment = {
    user: string,
    content: string,
    replies: Comment[],
}

type Reaction = {
    user: string,
    reaction: "laugh" | "cry" | "smile",
}

Run Code Online (Sandbox Code Playgroud)

我想要的是一个通用包装器,我可以将其与这些数据类型和任何其他数据类型一起使用,将每个字段替换user为其他内容。我可以为顶层做到这一点:

type UserFilled<T> = Omit<"user", T> & { user: { id: string, name: string }}
Run Code Online (Sandbox Code Playgroud)

但这只会改变user《华盛顿邮报》的领域。我还希望它能够爬下来并替换user每个评论的更改字段,如果有更多字段,则用于反应、点赞或任何其他带有 a 的结构user

我已经看到这个关于递归省略某些内容的答案,但我无法使用联合将修改后的属性添加回来,我想知道是否有更直接的方法来做到这一点,同时不仅省略而且替换字段?

例如,使用通用我希望能够做到这一点:

const post: Post = {
    user: "1234",
    content: "this is a post",
    comments: [{
        user: "3456",
        content: "I agree",
        replies: [{
            user: "1234",
            content: "thanks",
        }],
    }],
    reactions: [{
        user: "5678",
        reaction: "smile",
    }],
};

const postWUserInfo: UserFilled<Post> = {
    user: { id: "1234", name: "Bob" },
    content: "this is a post",
    comments: [{
        user: { id: "3456", name: "Jim" },
        content: "I agree",
        replies: [{
            user: { id: "1234", name: "Bob" },
            content: "thanks",
        }],
    }],
    reactions: [{
        user: { id: "5678", name: "Jim" },
        reaction: "smile",
    }],
};
Run Code Online (Sandbox Code Playgroud)

Bor*_*ipt 4

您可以创建一个 DeepReplace 实用程序来递归检查和替换密钥。另外,我强烈建议仅替换值并确保密钥保持不变。

// "not object"
type Primitive = string | Function | number | boolean | Symbol | undefined | null 

// If T has key K ("user"), replace it
type ReplaceKey<T, K extends string, R> = T extends Record<K, unknown> ? Omit<T, K> & Record<K, R> : T

// Check and replace object values
type DeepReplaceHelper<T, K extends string, R, ReplacedT = ReplaceKey<T, K, R>> = {
    [Key in keyof ReplacedT]: ReplacedT[Key] extends Primitive ? ReplacedT[Key] : ReplacedT[Key] extends unknown[] ? DeepReplace<ReplacedT[Key][number], K, R>[] : DeepReplace<ReplacedT[Key], K, R>
}

// T = object, K = key to replace, R = replacement value
type DeepReplace<T, K extends string, R> = T extends Primitive ? T : DeepReplaceHelper<T, K, R>

// Define new type for "user" key
interface UserReplacement {
    id: string
    name: string
}


type UserFilled<T> = DeepReplace<T, "user", UserReplacement>
Run Code Online (Sandbox Code Playgroud)

Typescript Playground 提供逐步说明