详细介绍:GraphQL:让前端自己决定要什么数据

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

目录

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

真实场景

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

  • 用户名
  • 头像
  • 粉丝数

使用 REST API

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

后端返回的数据

[
{
"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 条帖子

// 第 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 服务端

mkdir graphql-demo
cd graphql-demo
npm init -y
npm install apollo-server graphql

创建服务端 server.js

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}`);
});

启动服务器

node server.js
# 访问 http://localhost:4000

第二步:前端查询数据

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

查询所有用户

query {
  users {
    id
    username
    avatar
    followers
  }
}

返回结果

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

只要部分字段

query {
  users {
    username
    followers
  }
}

返回结果

{
"data": {
"users": [
{
"username": "张三",
"followers": 1234
},
{
"username": "李四",
"followers": 5678
}
]
}
}

看到了吗?

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

四、GraphQL 核心概念

三大核心

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

1️⃣ Schema:数据类型定义

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

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 就像点菜,告诉后端你要什么。

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

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

3️⃣ Mutation:修改数据

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

mutation {
  createUser(
    username: "王五"
    email: "wangwu@example.com"
  ) {
    id
    username
  }
}

五、Query 查询:获取数据

场景 1:只要部分字段

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

query {
  users {
    username
    followers
  }
}

REST 对比

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

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

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

query {
  user(id: "1") {
    username
    avatar
    posts {
      title
      createdAt
    }
  }
}

REST 对比

// 需要 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:使用变量

query GetUser($userId: ID!) {
  user(id: $userId) {
    username
    email
  }
}
# 变量
{
  "userId": "1"
}

场景 4:别名

query {
  user1: user(id: "1") {
    username
  }
  user2: user(id: "2") {
    username
  }
}
# 返回
{
  "data": {
    "user1": { "username": "张三" },
    "user2": { "username": "李四" }
  }
}

场景 5:片段(Fragment)

复用字段定义:

fragment UserInfo on User {
  id
  username
  avatar
  followers
}
query {
  user1: user(id: "1") {
    ...UserInfo
  }
  user2: user(id: "2") {
    ...UserInfo
  }
}

六、Mutation 变更:修改数据

创建数据

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

后端实现

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;
}
}
};

更新数据

mutation {
  updateUser(
    id: "1"
    username: "新名字"
  ) {
    id
    username
  }
}

删除数据

mutation {
  deleteUser(id: "1") {
    id
    username
  }
}

七、GraphQL vs REST 对比

功能对比

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

形象对比

REST API

就像套餐:
- A 套餐:汉堡 + 薯条 + 可乐
- B 套餐:鸡腿 + 薯条 + 雪碧
- C 套餐:...
你想要汉堡 + 雪碧?
对不起,没有这个套餐组合。

GraphQL

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

实际例子对比

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

REST 方式

// 请求 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 方式

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

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(推荐)

npm install @apollo/client graphql
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

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

常见安全问题

问题 1:查询深度攻击

# 恶意查询:无限嵌套
query {
  user {
    posts {
      author {
        posts {
          author {
            posts {
              # 无限嵌套...
            }
          }
        }
      }
    }
  }
}

解决方案:限制查询深度

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

问题 2:查询数量限制

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)

参考资料

posted on 2025-12-05 22:00  ljbguanli  阅读(0)  评论(0)    收藏  举报