GraphQL:让前端自己决定要什么数据

再也不用求后端"给我加个字段"了

📚 目录

  • [一、REST API 的痛点:要么吃不饱,要么撑死](#一、REST API 的痛点:要么吃不饱,要么撑死)
  • [二、GraphQL 是什么?](#二、GraphQL 是什么?)
  • [三、5 分钟快速上手](#三、5 分钟快速上手)
  • [四、GraphQL 核心概念](#四、GraphQL 核心概念)
  • [五、Query 查询:获取数据](#五、Query 查询:获取数据)
  • [六、Mutation 变更:修改数据](#六、Mutation 变更:修改数据)
  • [七、GraphQL vs REST 对比](#七、GraphQL vs REST 对比)
  • 八、常见问题和技巧

一、REST API 的痛点:要么吃不饱,要么撑死

🤔 真实场景

你在开发一个用户列表页面,需要显示:

  • 用户名
  • 头像
  • 粉丝数

使用 REST API

javascript 复制代码
// 调用用户列表接口
fetch('/api/users')
  .then(res => res.json())
  .then(users => {
    console.log(users);
  });

后端返回的数据

json 复制代码
[
  {
    "id": 1,
    "username": "张三",
    "avatar": "avatar1.jpg",
    "email": "zhangsan@example.com",     // 不需要
    "phone": "13800138000",              // 不需要
    "address": "北京市朝阳区...",         // 不需要
    "bio": "我是张三,很高兴...",         // 不需要
    "created_at": "2023-01-01",          // 不需要
    "updated_at": "2023-12-01",          // 不需要
    "followers": 1234,                   // 需要!
    "following": 567,                    // 不需要
    "posts_count": 89,                   // 不需要
    "likes_count": 456                   // 不需要
  }
]

😫 问题 1:过度获取(Over-fetching)

复制代码
你只需要:用户名、头像、粉丝数
后端给了你:12 个字段

就像:
- 你去饭店点了一碗面
- 服务员给你上了满汉全席
- 你:我吃不完啊!浪费流量!

😫 问题 2:获取不足(Under-fetching)

现在需求变了,还要显示最新的 3 条帖子

javascript 复制代码
// 第 1 个请求:获取用户列表
fetch('/api/users')

// 第 2 个请求:为每个用户获取帖子
users.forEach(user => {
  fetch(`/api/users/${user.id}/posts?limit=3`)
});

// 如果有 20 个用户 → 发送了 21 个请求!

这就是著名的 N+1 问题

复制代码
你去餐厅点餐:
1. 先问:有什么菜?
2. 服务员:有鱼香肉丝、宫保鸡丁...
3. 你:鱼香肉丝用什么肉?
4. 服务员去厨房问...回来告诉你
5. 你:宫保鸡丁辣吗?
6. 服务员又去厨房问...

来来回回跑了 N 次,效率低下!

😫 问题 3:接口爆炸

复制代码
需求不断变化 →接口越来越多:

/api/users              # 用户列表
/api/users/simple       # 简单用户信息
/api/users/detail       # 详细用户信息
/api/users/with-posts   # 用户+帖子
/api/users/with-followers # 用户+粉丝
...

后端:写了 100 个接口,累死了 😫
前端:不知道该用哪个接口 😵

二、GraphQL 是什么?

💡 一句话解释

GraphQL 就像点菜:你想要什么,就点什么,后端按需提供。

REST API

复制代码
你:给我用户信息
后端:给你全部信息(12 个字段)
你:我只要 3 个字段啊...

GraphQL

复制代码
你:我要用户的 username、avatar、followers
后端:好的,只给你这 3 个
你:太棒了!

🎯 核心特点

  1. 按需获取:要什么字段,就返回什么字段
  2. 一次请求:多个资源一次性获取
  3. 强类型:Schema 定义清晰
  4. 自文档:自带文档和自动补全

🏛️ GraphQL 简介

  • 开发者:Facebook(2012 年内部使用,2015 年开源)
  • 官网https://graphql.org/
  • 使用公司:Facebook、GitHub、Twitter、Shopify、Netflix

三、5 分钟快速上手

🎬 第一步:搭建 GraphQL 服务端

bash 复制代码
mkdir graphql-demo
cd graphql-demo
npm init -y
npm install apollo-server graphql

创建服务端 server.js

javascript 复制代码
const { ApolloServer, gql } = require('apollo-server');

// 1. 定义数据类型(Schema)
const typeDefs = gql`
  type User {
    id: ID!
    username: String!
    avatar: String
    followers: Int
  }

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

// 2. 模拟数据
const users = [
  { id: '1', username: '张三', avatar: 'avatar1.jpg', followers: 1234 },
  { id: '2', username: '李四', avatar: 'avatar2.jpg', followers: 5678 },
];

// 3. 定义如何获取数据(Resolvers)
const resolvers = {
  Query: {
    users: () => users,
    user: (parent, args) => users.find(u => u.id === args.id),
  },
};

// 4. 启动服务器
const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 服务器启动成功: ${url}`);
});

启动服务器

bash 复制代码
node server.js
# 访问 http://localhost:4000

🎬 第二步:前端查询数据

打开 http://localhost:4000,你会看到 GraphQL Playground(在线调试工具)。

查询所有用户

graphql 复制代码
query {
  users {
    id
    username
    avatar
    followers
  }
}

返回结果

json 复制代码
{
  "data": {
    "users": [
      {
        "id": "1",
        "username": "张三",
        "avatar": "avatar1.jpg",
        "followers": 1234
      },
      {
        "id": "2",
        "username": "李四",
        "avatar": "avatar2.jpg",
        "followers": 5678
      }
    ]
  }
}

只要部分字段

graphql 复制代码
query {
  users {
    username
    followers
  }
}

返回结果

json 复制代码
{
  "data": {
    "users": [
      {
        "username": "张三",
        "followers": 1234
      },
      {
        "username": "李四",
        "followers": 5678
      }
    ]
  }
}

看到了吗?

  • 你要什么字段,就返回什么字段
  • 不多也不少,刚刚好!

四、GraphQL 核心概念

🎨 三大核心

复制代码
Schema(菜单)  → 定义有哪些菜
Query(点菜)   → 查询数据
Mutation(加菜)→ 修改数据

1️⃣ Schema:数据类型定义

Schema 就像餐厅的菜单,告诉你有什么菜。

graphql 复制代码
type User {
  id: ID!           # ID 类型,! 表示必填
  username: String! # 字符串,必填
  avatar: String    # 字符串,可选
  email: String
  followers: Int    # 整数
  posts: [Post]     # Post 数组
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User      # 关联到 User
}

type Query {
  users: [User]           # 查询用户列表
  user(id: ID!): User     # 查询单个用户
  posts: [Post]           # 查询帖子列表
}

类型说明

类型 说明 示例
ID 唯一标识 "1", "abc123"
String 字符串 "张三"
Int 整数 123
Float 浮点数 3.14
Boolean 布尔值 true, false
[Type] 数组 [User]
Type! 必填 String!

2️⃣ Query:查询数据

Query 就像点菜,告诉后端你要什么。

基础查询
graphql 复制代码
query {
  users {
    username
    followers
  }
}
带参数的查询
graphql 复制代码
query {
  user(id: "1") {
    username
    email
    followers
  }
}
嵌套查询
graphql 复制代码
query {
  user(id: "1") {
    username
    posts {
      title
      content
    }
  }
}

一次请求就能获取用户和他的帖子!

3️⃣ Mutation:修改数据

Mutation 就像下单,告诉后端要改什么。

graphql 复制代码
mutation {
  createUser(
    username: "王五"
    email: "wangwu@example.com"
  ) {
    id
    username
  }
}

五、Query 查询:获取数据

🎯 场景 1:只要部分字段

需求:只要用户名和粉丝数

graphql 复制代码
query {
  users {
    username
    followers
  }
}

REST 对比

javascript 复制代码
// REST:后端返回 12 个字段,你只用 2 个
fetch('/api/users')
  .then(res => res.json())
  .then(users => {
    users.map(u => ({
      username: u.username,
      followers: u.followers
    }))
  });

🎯 场景 2:多个资源一次获取

需求:用户信息 + 他的帖子

graphql 复制代码
query {
  user(id: "1") {
    username
    avatar
    posts {
      title
      createdAt
    }
  }
}

REST 对比

javascript 复制代码
// 需要 2 个请求
const user = await fetch('/api/users/1').then(r => r.json());
const posts = await fetch('/api/users/1/posts').then(r => r.json());

// GraphQL:1 个请求搞定!

🎯 场景 3:使用变量

graphql 复制代码
query GetUser($userId: ID!) {
  user(id: $userId) {
    username
    email
  }
}

# 变量
{
  "userId": "1"
}

🎯 场景 4:别名

graphql 复制代码
query {
  user1: user(id: "1") {
    username
  }
  user2: user(id: "2") {
    username
  }
}

# 返回
{
  "data": {
    "user1": { "username": "张三" },
    "user2": { "username": "李四" }
  }
}

🎯 场景 5:片段(Fragment)

复用字段定义:

graphql 复制代码
fragment UserInfo on User {
  id
  username
  avatar
  followers
}

query {
  user1: user(id: "1") {
    ...UserInfo
  }
  user2: user(id: "2") {
    ...UserInfo
  }
}

六、Mutation 变更:修改数据

🎯 创建数据

graphql 复制代码
mutation {
  createUser(
    username: "王五"
    email: "wangwu@example.com"
    avatar: "avatar.jpg"
  ) {
    id
    username
  }
}

后端实现

javascript 复制代码
const resolvers = {
  Mutation: {
    createUser: (parent, args) => {
      const newUser = {
        id: String(users.length + 1),
        username: args.username,
        email: args.email,
        avatar: args.avatar,
        followers: 0
      };
      users.push(newUser);
      return newUser;
    }
  }
};

🎯 更新数据

graphql 复制代码
mutation {
  updateUser(
    id: "1"
    username: "新名字"
  ) {
    id
    username
  }
}

🎯 删除数据

graphql 复制代码
mutation {
  deleteUser(id: "1") {
    id
    username
  }
}

七、GraphQL vs REST 对比

📊 功能对比

特性 REST GraphQL
获取数据 固定字段 按需获取
请求次数 多个请求 一次请求
字段控制 后端决定 前端决定
接口数量 很多接口 一个端点
版本管理 v1, v2, v3 无需版本
文档 需要手写 自动生成

🎭 形象对比

REST API

复制代码
就像套餐:
- A 套餐:汉堡 + 薯条 + 可乐
- B 套餐:鸡腿 + 薯条 + 雪碧
- C 套餐:...

你想要汉堡 + 雪碧?
对不起,没有这个套餐组合。

GraphQL

复制代码
就像自助餐:
- 你想吃什么,就拿什么
- 汉堡、薯条、可乐、雪碧...随便组合
- 吃多少拿多少,不浪费

📝 实际例子对比

需求:获取用户信息和最新 3 条帖子

REST 方式

javascript 复制代码
// 请求 1:获取用户
const user = await fetch('/api/users/1').then(r => r.json());

// 请求 2:获取帖子
const posts = await fetch('/api/users/1/posts?limit=3').then(r => r.json());

// 需要 2 个请求

GraphQL 方式

graphql 复制代码
query {
  user(id: "1") {
    username
    avatar
    posts(limit: 3) {
      title
      createdAt
    }
  }
}

# 只需 1 个请求!

八、常见问题和技巧

❓ FAQ

Q1: GraphQL 适合什么场景?

✅ 适合

  • 移动端 App(流量宝贵,按需获取)
  • 复杂的数据关系(用户-帖子-评论)
  • 多客户端(iOS、Android、Web 需求不同)
  • 快速迭代(前端自己加字段,不用等后端)

❌ 不适合

  • 简单的 CRUD(REST 更简单)
  • 文件上传(用 REST 更合适)
  • 实时性要求极高(考虑 WebSocket)
Q2: GraphQL 会取代 REST 吗?

不会! 它们是互补的:

复制代码
REST:简单场景
GraphQL:复杂场景

就像:
- 买瓶水 → 去便利店(REST)
- 买一周的菜 → 去超市(GraphQL)
Q3: 如何在前端使用 GraphQL?

方式 1:原生 fetch

javascript 复制代码
fetch('http://localhost:4000/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: `
      query {
        users {
          username
          followers
        }
      }
    `
  })
})
.then(res => res.json())
.then(data => console.log(data));

方式 2:使用 Apollo Client(推荐)

bash 复制代码
npm install @apollo/client graphql
javascript 复制代码
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

// 创建客户端
const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql',
  cache: new InMemoryCache()
});

// 查询数据
client.query({
  query: gql`
    query {
      users {
        username
        followers
      }
    }
  `
})
.then(result => console.log(result.data));
Q4: 如何调试 GraphQL?

工具 1:GraphQL Playground

访问 http://localhost:4000,会自动打开 Playground。

工具 2:Apollo Client DevTools

Chrome 插件,可以查看:

  • 查询历史
  • 缓存状态
  • 网络请求

工具 3:console.log

javascript 复制代码
const resolvers = {
  Query: {
    users: () => {
      console.log('有人查询 users');
      return users;
    }
  }
};
Q5: GraphQL 安全吗?

常见安全问题

问题 1:查询深度攻击

graphql 复制代码
# 恶意查询:无限嵌套
query {
  user {
    posts {
      author {
        posts {
          author {
            posts {
              # 无限嵌套...
            }
          }
        }
      }
    }
  }
}

解决方案:限制查询深度

javascript 复制代码
const { createComplexityLimitRule } = require('graphql-validation-complexity');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000) // 限制复杂度
  ]
});

问题 2:查询数量限制

javascript 复制代码
const resolvers = {
  Query: {
    users: (parent, args) => {
      const limit = args.limit || 10;
      // 最多返回 100 条
      return users.slice(0, Math.min(limit, 100));
    }
  }
};

🎓 总结

核心要点回顾

1. GraphQL 是什么?
复制代码
一种查询语言,让前端按需获取数据
2. 解决什么问题?
复制代码
✅ 过度获取(要 3 个字段,给了 12 个)
✅ 获取不足(要关联数据,需要多次请求)
✅ 接口爆炸(100 个接口)
3. 核心概念
复制代码
Schema(定义数据结构)
Query(查询数据)
Mutation(修改数据)
Resolver(如何获取数据)
4. 何时使用?
复制代码
✅ 移动端(省流量)
✅ 复杂数据关系
✅ 多客户端
❌ 简单 CRUD
❌ 文件上传

🎯 一句话总结

GraphQL 就像自助餐,你想吃什么就拿什么,想拿多少拿多少。REST 是套餐,只能选 A、B、C。

💡 下一步

  1. 学习 Apollo Client(前端库)
  2. 学习订阅(Subscription)- 实时数据
  3. 学习缓存策略
  4. 学习性能优化(DataLoader)

📖 参考资料

相关推荐
冴羽2 小时前
为什么在 JavaScript 中 NaN !== NaN?背后藏着 40 年的技术故事
前端·javascript·node.js
久爱@勿忘2 小时前
vue下载项目内静态文件
前端·javascript·vue.js
前端炒粉2 小时前
21.搜索二维矩阵 II
前端·javascript·算法·矩阵
码事漫谈2 小时前
C++双向链表删除操作:由浅入深完全指南
后端
码事漫谈2 小时前
软件生产的“高速公路网”:深入浅出理解CI/CD的核心流程
后端
合作小小程序员小小店2 小时前
web网页开发,在线%台球俱乐部管理%系统,基于Idea,html,css,jQuery,jsp,java,ssm,mysql。
java·前端·jdk·intellij-idea·jquery·web
不爱吃糖的程序媛2 小时前
Electron 应用中的系统检测方案对比
前端·javascript·electron
泷羽Sec-静安2 小时前
Less-9 GET-Blind-Time based-Single Quotes
服务器·前端·数据库·sql·web安全·less
pe7er3 小时前
用高阶函数实现递归:从匿名函数到通用递归生成器
前端·javascript