再也不用求后端"给我加个字段"了
📚 目录
- [一、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 个
你:太棒了!
🎯 核心特点
- 按需获取:要什么字段,就返回什么字段
- 一次请求:多个资源一次性获取
- 强类型:Schema 定义清晰
- 自文档:自带文档和自动补全
🏛️ 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。
💡 下一步
- 学习 Apollo Client(前端库)
- 学习订阅(Subscription)- 实时数据
- 学习缓存策略
- 学习性能优化(DataLoader)