GraphQL 简介

查询:参数、变量与嵌套

掌握 GraphQL 查询的常用技能,学会字段参数、查询变量、别名,以及一次请求嵌套获取关联数据。

🎯 引言

掌握了 Schema 之后,我们把视角切回客户端:写查询不只是「挑字段」,实际开发中还要按条件查数据、复用查询语句、一次拿回互相关联的多层数据。

学完这篇文章,你能熟练使用查询的四个常用技能:给字段传参数、用变量动态传值、用别名重复查询同一字段,以及用嵌套查询一次取回关联数据。这些技能覆盖了日常查询的大部分场景。


🧱 字段参数

查询字段可以像函数一样接收参数。比如「按 id 查单个用户」,先在 Schema 的 Query 里声明参数:

type Query {
    users: [User!]!
    user(id: ID!): User
}

user(id: ID!) 表示这个查询必须传一个 ID 类型的 id 参数。客户端这样写:

{
    user(id: "1") {
        username
        age
    }
}

服务端这边,参数通过 resolver 函数的第二个参数 args 拿到:

const resolvers = {
    Query: {
        user: (parent, args) => users.find((item) => item.id === args.id),
    },
};

resolver 函数有固定的参数结构,常用的是前两个:

  • parent:上一层解析出来的数据,顶层查询用不到,嵌套时很关键,后面会看到。
  • args:客户端传来的参数对象,客户端传了什么这里就有什么。
参数的类型和是否必传(!)都写在 Schema 里。客户端少传了必传参数,或者类型不对,执行前就会校验失败,不需要我们在 resolver 里手动检查。

✨ 查询变量

上面的例子里,"1" 是直接写死在查询语句里的。真实项目中 id 来自用户点击,是动态的,总不能每次拼字符串。GraphQL 为此提供了**变量(Variables)**机制:

query GetUser($id: ID!) {
    user(id: $id) {
        username
        age
    }
}

几个新语法:

  • query GetUser:给这次查询起个名字(操作名),GetUser 方便调试和日志排查。
  • ($id: ID!):声明一个变量 $id,类型是 ID 且必传。变量名以 $ 开头。
  • user(id: $id):在查询里使用这个变量。

变量的值不写在查询语句里,而是作为独立的 JSON 一起发给服务端。在 Apollo Sandbox 左下角的 Variables 面板里填:

{
    "id": "1"
}

这样做有两个明显的好处:查询语句本身可以固定下来复用,只有值在变;同时避免了拼字符串带来的转义问题,和 SQL 里用预编译参数防注入是同一个思路。


⚡ 别名:一次查多份

有时候想在一次请求里对比两个用户,直接写两遍 user 会字段冲突:

{
    first: user(id: "1") {
        username
    }
    second: user(id: "2") {
        username
    }
}

**别名(Alias)**的语法是 别名: 原字段,返回结果的键名也会变成别名:

{
    "data": {
        "first": { "username": "小明" },
        "second": { "username": "小美" }
    }
}

别名不光用于带参数字段,任何字段重名冲突时都可以用它解决,是查询里很顺手的小工具。


🛠 嵌套查询与 resolver 链

GraphQL 一次取回关联数据的能力,是它相比 REST 的一个突出优势。上一篇我们在 Schema 里写过 UserArticle 的关联,现在把它实现完整。

先是模拟数据,文章里用 authorId 记录作者:

data.js
export const users = [
    { id: '1', username: '小明', age: 20 },
    { id: '2', username: '小美', age: 22 },
];

export const articles = [
    { id: '1', title: 'GraphQL 入门', authorId: '1' },
    { id: '2', title: 'Vue3 实战', authorId: '1' },
    { id: '3', title: 'Node.js 基础', authorId: '2' },
];

Schema 里让 User 带上 articles 字段:

type Article {
    id: ID!
    title: String!
}

type User {
    id: ID!
    username: String!
    articles: [Article!]!
}

关键在 resolvers。注意 users 数组里的用户对象并没有 articles 属性,所以需要给 User 类型单独写一个 resolver,告诉 GraphQL「用户的文章怎么取」:

const resolvers = {
    Query: {
        users: () => users,
        user: (parent, args) => users.find((item) => item.id === args.id),
    },
    User: {
        articles: (parent) =>
            articles.filter((item) => item.authorId === parent.id),
    },
};

这里 parent 参数就登场了:当客户端查询某个用户的 articles 时,parent 就是上一层解析出来的那个用户对象,我们用 parent.id 去文章数组里过滤出他的作品。

整个过程像接力赛:Query.users 先跑出用户列表,然后对每个用户,User.articles 接过「棒」(parent),跑出他的文章。客户端只需要一条查询:

{
    users {
        username
        articles {
            title
        }
    }
}

就能拿到「每个用户带着自己文章列表」的完整结构。换成 REST,这通常要先请求用户列表,再逐个请求文章接口,来回好几趟。

只有当前类型本身没有的字段才需要写类型级 resolver(如 User.articles)。像 username 这种数据里本来就有的字段,GraphQL 会默认从对象上按字段名取,不用写。

🧾 小节总结

  • 查询字段可以声明参数,如 user(id: ID!),resolver 通过第二个参数 args 接收。
  • $变量 加独立 JSON 的方式动态传值,查询语句可复用,避免拼字符串。
  • 别名: 字段 可以在一次请求里重复查询同一字段,返回键名随别名。
  • 关联数据通过嵌套查询一次取回,类型级 resolver 用第一个参数 parent 拿到上一层数据。
  • 数据对象上已有的字段不用写 resolver,GraphQL 默认按字段名取值。

❓ 知识问答

Q1:操作名 GetUser 是必须的吗?

不是必须的,但建议养成习惯。服务端日志里会记录操作名,线上排查问题时能快速定位是哪次查询出的问题。

Q2:变量能传对象、数组吗?

可以。变量支持任何 Schema 里声明的类型,包括后面会讲的 input 输入对象类型,Mutation 传复杂数据时就靠它。

Q3:嵌套查询会不会有性能问题?

有可能。比如每个用户的 articles 都去查一次数据库,100 个用户就是 100 次查询,这就是常说的 N+1 问题。学习阶段不用担心,了解这个概念即可,实际项目可以用 DataLoader 批量合并查询。

Q4:参数能传多个吗?

可以,逗号分隔,例如 articles(first: Int, after: String)。resolver 里通过 args.firstargs.after 取用,常用于分页。

Q5:parent 在顶层 Query 的 resolver 里是什么?

顶层查询没有「上一层」,parent 是一个基本用不到的根值,所以惯例上直接用不到就不写它,或者写成 _ 占位。


🧪 小练习

练习一:给 Query 增加一个 article(id: ID!): Article 查询,用变量写出查询语句,在 Sandbox 里分别传入 "1""99",观察找不到时返回什么。

# 请在这里编写代码

练习二:模仿 User.articles 的写法,给 Article 类型加一个 author: User! 字段并写出它的 resolver,然后在一次查询里取出「每篇文章的标题和作者名」。

const resolvers = {
    Article: {
        // 请在这里编写代码
    },
};

🎉 恭喜你已经掌握 GraphQL 查询的常用技能啦!目前我们的数据还是只读的,下一篇学习 Mutation,实现数据的新增、更新和删除。