在GraphQL中使用别名

1,467 阅读8分钟

如果你在你的生产应用中使用了GraphQL,你可能遇到过这样的情况:你需要你的自定义查询来返回自定义字段名。或者,也许你在同一个服务器响应中收到了两个查询结果,导致字段名发生冲突。

在这些情况下,我们可以使用GraphQL别名来改进我们的查询。在本教程中,我们将介绍什么是别名,它是如何工作的,以及何时应该使用它。我们将研究几种情况,并通过一个相关的例子来扩展这个概念。让我们开始吧!

什么是GraphQL别名?

别名允许我们重新命名查询结果中返回的数据。别名不会改变原来的模式,相反,它们会操纵从数据库中获取的查询结果的结构,根据你的规范来显示它。

当你想提高你的查询效率时,别名就会很方便。例如,假设你试图通过应用各种过滤器来获取相同的数据。许多开发者会倾向于写单独的查询,并在独立的API调用中执行它们。在这种情况和其他情况下,别名可以帮助你优化和改善你的查询组织。

有几种情况下,你可能想改变你接收查询结果的字段的名称。让我们看一下两个不同的例子。

在一个查询中获取多个对象

使用别名,你可以在一个单一的GraphQL查询中结合对同一对象的多次获取。

比方说,你正在建立一个应用程序,在一个feed中显示一个帖子列表。在你的应用程序中,一个典型的帖子看起来像下面的代码块。

type Post {
  id: string
  parent: string
  type: string # POST or COMMENT
  author: string
  title: string
  text: string
  createdAt: string
}

为了简单起见,我们将自己限制在每个字段的字符串数据类型上。理想情况下,你会为type 字段使用一个枚举,为createdAt 字段使用一个自定义日期标量。

请注意,我们定义了一个type 字段,以适应同一表格中不同类型的帖子。在我们的例子中,我们有帖子和评论。

getPosts 的查询看起来像下面的代码块。

query getPosts {
  posts {
    id
    parent
    type
    author
    title
    text
    createdAt
  }
}

现在,让我们假设你的数据库中有一个帖子和一个评论。如果你运行上面的查询,你会得到一个类似于下面代码块的响应。

{
  "data": {
    "posts": [
      {
        "id": "s9d8fhsd-fsdf",
        "parent": null,
        "type": "POST",
        "author": "Korg",
        "title": "Let's start a revolution",
        "text": "Hey, man. I'm Korg. We're gonna get outta here on that big spaceship. Wanna come?",
        "createdAt": "11:06 AM IST, Aug 7 2021"
      },
      {
        "id": "d8g6dffd-jfod",
        "parent": "s9d8fhsd-fsdf",
        "type": "COMMENT",
        "author": "Loki",
        "title": null,
        "text": "Well, it seems that you are in dire need of leadership.",
        "createdAt": "11:09 AM IST, Aug 7 2021"
      }
    ]
  }
}

你的数据库也可能包含一长串的帖子和评论,就像上面的例子。你可能会尝试写一个查询,在一个分离的帖子和评论列表中获取这些帖子。

query getPosts {
  posts(type: "POST") {
    id
    author
    title
    text
    createdAt
  }
  posts(type: "COMMENT") {
    id
    parent
    author
    text
    createdAt
  }
}

然而,你会发现,这个查询根本没有运行,它抛出了以下错误。

{
  "errors": [
    {
      "message": "Fields 'posts' conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.",
      "locations": [
        {
          "line": 2,
          "column": 3
        },
        {
          "line": 9,
          "column": 3
        }
      ]
    }
  ]
}

当执行时,该查询为同一个字段名返回两个结果列表posts 。使用别名,我们可以将字段名改为两个不同的关键字,使查询完美地运行。

query getPosts {
  posts: posts(type: "POST") {
    id
    author
    title
    text
    createdAt
  }
  comments: posts(type: "COMMENT") {
    id
    parent
    author
    text
    createdAt
  }
}

现在,你可以很容易地在同一个GraphQL查询中进行多次获取调用,节省网络使用,降低代码的复杂性。

为你的查询结果增加意义

一个不言自明的名字让任何开发者都能很好地理解你的代码。对于数据库结果来说也是如此。你需要确保处理API响应的开发人员熟知数据的含义。让我们学习如何为我们的查询结果添加意义,以帮助提供有用的命名规则。

让我们重新考虑一下我们先前的例子。我们的模式看起来像下面的代码块。

type Post {
  id: string
  type: string # POST or COMMENT
  author: string
  title: string
  text: string
  createdAt: string
}

该模式包含一个父字段,旨在将一个评论与一个帖子联系起来,定义哪个帖子是评论的父。虽然这对数据库工程师来说应该是有意义的,但对前端工程师来说,可能不是马上就能明白。

在这种情况下,你可以构造你的传统查询,以更好地描述父属性。

query getPosts {
  posts {
    id
    parentPost: parent
    type
    author
    title
    text
    createdAt
  }
}

你可以按照你的意愿重命名其他属性。在执行查询后返回的结果中,原始名称将被完全覆盖。一个典型的响应将看起来像下面的代码块。

{
  "data": {
    "posts": [
      {
        "id": "s9d8fhsd-fsdf",
        "parentPost": null,
        "type": "POST",
        "author": "Korg",
        "title": "Let's start a revolution",
        "text": "Hey, man. I'm Korg. We're gonna get outta here on that big spaceship. Wanna come?",
        "createdAt": "11:06 AM IST, Aug 7 2021"
      },
      {
        "id": "d8g6dffd-jfod",
        "parentPost": "s9d8fhsd-fsdf",
        "type": "COMMENT",
        "author": "Loki",
        "title": null,
        "text": "Well, it seems that you are in dire need of leadership.",
        "createdAt": "11:09 AM IST, Aug 7 2021"
      }
    ]
  }
}

GraphQL别名的最佳实践

虽然在GraphQL中实现别名是相当直接的,但不恰当的使用会给你的应用程序带来巨大的破坏。为了优化GraphQL别名的结果,一定要牢记以下几点。

不言自明的命名

因为别名允许你改变你的结果的字段名,所以必须确保你选择的名字对实际数据有意义。

尽管选择一个方便的、简短的、你容易理解的名字是很诱人的,但你也必须考虑到其他开发者可能会在你之后从事这个项目。除非你的命名约定是不言自明的,否则他们可能很难理解你的代码,有可能在你的项目中造成错误。

例如,下面的代码就包含了一个命名不当的别名的例子。

query getPosts {
  p: posts(type: "POST") {
    id
    author
    title
    text
    createdAt
  }
  c: posts(type: "COMMENT") {
    id
    parent
    author
    text
    createdAt
  }
}

将两个中间查询结果命名为p ,以表示帖子,而将评论命名为c ,这似乎很方便,然而,让我们看看上述查询的结果。

{
  "data": {
    "p": [
      {
        "id": "s9d8fhsd-fsdf",
        "author": "Korg",
        "title": "Let's start a revolution",
        "text": "Hey, man. I'm Korg. We're gonna get outta here on that big spaceship. Wanna come?",
        "createdAt": "11:06 AM IST, Aug 7 2021"
      },
    ],
    "c": [
      {
        "id": "d8g6dffd-jfod",
        "parent": "s9d8fhsd-fsdf",
        "author": "Loki",
        "text": "Well, it seems that you are in dire need of leadership.",
        "createdAt": "11:09 AM IST, Aug 7 2021"
      }
    ]
  }
}

正如你所看到的,如果没有关于结果类型的上下文信息,就很难理解pc 是什么意思。因此,在这种情况下,坚持使用全名postscomments 将是一个更好的选择。

只在需要时使用别名

别名只是一个现有字段的假名,所以即使不一定需要,也可能很想使用它们。然而,重命名会导致你的代码中出现不必要的映射。例如,让我们以我们的getPosts 查询为例。

query getPosts {
  posts {
    id
    parent
    type
    author
    title
    text
    createdAt
  }
}

posts 定义中的每个字段都是不言自明的。然而,你可能想为每个字段添加一个更有力的描述符,如下所示。

query getPosts {
  posts {
    postId: id
    parentPost: parent
    postType: type
    author
    title
    postContent: text
    createdAt
  }
}

请记住,每次重命名时,你都需要在你的代码中传播新的名称。在添加别名之前,你应该考虑额外的细节是否有用。我建议只有在解决了一个反复出现的问题时才使用别名。

进行服务器端修改

如果你发现自己反复重命名相同的字段,你可能要考虑放弃别名,在服务器本身重命名该字段。同样,如果你不得不在你的客户端频繁使用别名,你可能需要减少你的数据库模式。

别名是为了方便快速重命名,然而,你不应该严重依赖它们。如果你发现自己经常使用别名,那么你的服务器端的命名规则可能有问题。在这种情况下,使用别名只会使代码库更加复杂。

总结

别名是重新组织GraphQL查询结果的好方法。当你的后端数据模型和前端数据规范不完全吻合,你需要手动调整它们时,别名尤其有用。

当你需要在同一来源上一次性获取多个查询结果时,别名是必须的。然而,少用别名也是必要的。虽然别名大多是无害的,而且只在数据被返回后才会改变,但不必要的使用会导致数据模式的混乱,甚至导致错误。

我希望你喜欢这个教程

GraphQL中使用别名一文首次出现在LogRocket博客上。