GraphQL 入门与应用指南
1. GraphQL 查询基础
当一个字段返回复杂类型(如
product
)时,查询必须指定所需的字段。例如,以下查询请求服务器提供每个供应商对象的
id
、
name
和
city
字段,以及每个相关产品对象的
name
字段:
query {
suppliers {
id
name
city
products {
name
}
}
}
执行此查询,将得到如下结果:
{
"data": {
"suppliers": [
{
"id": "1",
"name": "Surf Dudes",
"city": "San Jose",
"products": [
{ "name": "Kayak" },
{ "name": "Lifejacket" }
]
},
{
"id": "2",
"name": "Goal Oriented",
"city": "Seattle",
"products": [
{ "name": "Soccer Ball" },
{ "name": "Corner Flags" },
{ "name": "Stadium" }
]
},
{
"id": "3",
"name": "Bored Games",
"city": "New York",
"products": [
{ "name": "Thinking Cap" },
{ "name": "Unsteady Chair" },
{ "name": "Human Chess Board" },
{ "name": "Bling Bling King" }
]
}
]
}
}
注意,客户端明确指定了供应商对象和相关产品数据所需的字段,确保只获取应用程序所需的数据。
除了常规查询,GraphQL 规范还支持订阅(subscriptions),用于获取服务器上变化数据的持续更新。不过,订阅功能的支持并不广泛和一致。
2. 创建带参数的查询
当前 GraphQL 服务器提供的查询允许用户选择所需的字段,但不能选择结果中的对象。为了让客户端能够自定义请求,GraphQL 支持参数。以下是在
schema.graphql
文件中定义带参数查询的示例:
type product {
id: ID!
name: String!
category: String!
price: Float!
}
type supplier {
id: ID!
name: String!
city: String!
products: [product]
}
type Query {
products: [product]
product(id: ID!): product
suppliers: [supplier]
supplier(id: ID!): supplier
}
参数在查询名称后的括号中定义,每个参数都有一个名称和类型。在上述示例中,
product
和
supplier
查询都定义了一个
id
参数,类型为
ID
,并使用感叹号表示该参数是必需的。
以下是在
resolvers.js
文件中为这些查询添加解析器的示例:
var data = require("../../restData")();
module.exports = {
products: () => data.products,
product: (args) => data.products.find(p => p.id === parseInt(args.id)),
suppliers: () => data.suppliers.map(s => ({
...s,
products: () => s.products.map(id =>
data.products.find(p => p.id === Number(id))
)
})),
supplier: (args) => {
const result = data.suppliers.find(s => s.id === parseInt(args.id));
if (result) {
return {
...result,
products: () => result.products.map(id =>
data.products.find(p => p.id === Number(id))
)
};
}
}
};
解析器函数接收一个对象,其属性对应查询参数。为了获取查询中指定的
id
值,解析器函数读取
args.id
属性。可以通过解构参数对象来简化代码:
var data = require("../../restData")();
module.exports = {
products: () => data.products,
product: ({ id }) => data.products.find(p => p.id === parseInt(id)),
suppliers: () => data.suppliers.map(s => ({
...s,
products: ({ nameFilter }) => mapIdsToProducts(s, nameFilter)
})),
supplier: ({ id }) => {
const result = data.suppliers.find(s => s.id === parseInt(id));
if (result) {
return {
...result,
products: ({ nameFilter }) => mapIdsToProducts(result, nameFilter)
};
}
}
};
重启 GraphQL 服务器,并在 GraphiQL 窗口中输入以下查询:
query {
supplier(id: 1) {
id
name
city
products {
name
}
}
}
此查询请求
id
为 1 的供应商对象,并获取其
id
、
name
和
city
字段,以及相关产品的
name
字段,结果如下:
{
"data": {
"supplier": {
"id": "1",
"name": "Surf Dudes",
"city": "San Jose",
"products": [
{ "name": "Kayak" },
{ "name": "Lifejacket" }
]
}
}
}
3. 为字段添加参数
可以为单个字段定义参数,使客户端能够更精确地指定所需的数据。以下是在
schema.graphql
文件中为
supplier
类型的
products
字段添加参数的示例:
type product {
id: ID!
name: String!
category: String!
price: Float!
}
type supplier {
id: ID!
name: String!
city: String!
products(nameFilter: String = ""): [product]
}
type Query {
products: [product]
product(id: ID!): product
suppliers: [supplier]
supplier(id: ID!): supplier
}
products
字段重新定义为接收一个字符串类型的
nameFilter
参数,该参数是可选的,默认值为空字符串。
以下是在
resolvers.js
文件中实现该字段参数的示例:
var data = require("../../restData")();
const mapIdsToProducts = (supplier, nameFilter) =>
supplier.products.map(id => data.products.find(p => p.id === Number(id)))
.filter(p => p.name.toLowerCase().includes(nameFilter.toLowerCase()));
module.exports = {
products: () => data.products,
product: ({ id }) => data.products.find(p => p.id === parseInt(id)),
suppliers: () => data.suppliers.map(s => ({
...s,
products: ({ nameFilter }) => mapIdsToProducts(s, nameFilter)
})),
supplier: ({ id }) => {
const result = data.suppliers.find(s => s.id === parseInt(id));
if (result) {
return {
...result,
products: ({ nameFilter }) => mapIdsToProducts(result, nameFilter)
};
}
}
};
重启 GraphQL 服务器,并在 GraphiQL 中输入以下查询:
query {
supplier(id: 1) {
id
name
city
products(nameFilter: "ak") {
name
}
}
}
执行查询后,将得到过滤后的结果,只包含名称中包含
ak
的产品:
{
"data": {
"supplier": {
"id": "1",
"name": "Surf Dudes",
"city": "San Jose",
"products": [
{ "name": "Kayak" }
]
}
}
}
需要注意的是,接收字段参数的方法会在每次请求时被调用,可能会给服务器带来大量工作。对于复杂结果,可以考虑使用缓存包,如
fast-memoize
。
由于字段参数是应用于类型而不是特定查询,因此该过滤器可用于任何包含相关产品数据的供应商数据查询。例如,以下查询将对所有供应商的相关产品数据进行过滤:
query {
suppliers {
id
name
city
products(nameFilter: "g") {
name
}
}
}
4. 执行 GraphQL 突变
突变(Mutations)用于请求 GraphQL 服务器对其数据进行更改。突变通过特殊的
Mutation
类型添加到模式中,有两种常见的方法。以下是在
schema.graphql
文件中定义突变的示例:
type product {
id: ID!
name: String!
category: String!
price: Float!
}
type supplier {
id: ID!
name: String!
city: String!
products(nameFilter: String = ""): [product]
}
type Query {
products: [product]
product(id: ID!): product
suppliers: [supplier]
supplier(id: ID!): supplier
}
input productInput {
id: ID
name: String!
category: String!
price: Int!
}
type Mutation {
storeProduct(product: productInput): product
storeSupplier(id: ID, name: String!, city: String!, products: [Int]): supplier
}
第一个突变
storeProduct
使用了一个专用的输入类型
productInput
,允许客户端提供描述所需更改的值。输入类型使用
input
关键字定义,支持与常规类型相同的功能。
第二个突变
storeSupplier
采用了简单的方法,直接定义多个参数,让客户端无需输入类型即可表达数据对象的详细信息。
以下是在
resolvers.js
文件中实现这些突变的示例:
var data = require("../../restData")();
const mapIdsToProducts = (supplier, nameFilter) =>
supplier.products.map(id => data.products.find(p => p.id === Number(id)))
.filter(p => p.name.toLowerCase().includes(nameFilter.toLowerCase()));
let nextId = 100;
module.exports = {
products: () => data.products,
product: ({ id }) => data.products.find(p => p.id === parseInt(id)),
suppliers: () => data.suppliers.map(s => ({
...s,
products: ({ nameFilter }) => mapIdsToProducts(s, nameFilter)
})),
supplier: ({ id }) => {
const result = data.suppliers.find(s => s.id === parseInt(id));
if (result) {
return {
...result,
products: ({ nameFilter }) => mapIdsToProducts(result, nameFilter)
};
}
},
storeProduct({ product }) {
if (product.id == null) {
product.id = nextId++;
data.products.push(product);
} else {
product = { ...product, id: Number(product.id) };
data.products = data.products.map(p => p.id === product.id ? product : p);
}
return product;
},
storeSupplier(args) {
const supp = { ...args, id: Number(args.id) };
if (args.id == null) {
supp.id = nextId++;
data.suppliers.push(supp);
} else {
data.suppliers = data.suppliers.map(s => s.id === supp.id ? supp : s);
}
let result = data.suppliers.find(s => s.id === supp.id);
if (result) {
return {
...result,
products: ({ nameFilter }) => mapIdsToProducts(result, nameFilter)
};
}
}
};
突变以函数的形式实现,接收参数,就像查询一样。这些突变使用
ID
字段来确定客户端是在更新现有对象还是存储新对象,并更新查询使用的展示数据以反映更改。
要使用
storeProduct
突变更新产品,重启服务器并在 GraphiQL 中输入以下 GraphQL 代码:
mutation {
storeProduct(product: {
id: 1
name: "Green Kayak"
category: "Watersports"
price: 290
}) {
id
name
category
price
}
}
执行该突变后,将得到如下结果:
{
"data": {
"storeProduct": {
"id": "1",
"name": "Green Kayak",
"category": "Watersports",
"price": 290
}
}
}
为了确认突变是否生效,可以在 GraphiQL 中执行以下查询:
query {
product(id: 1) {
id
name
category
price
}
}
执行该查询后,将看到反映突变更改的结果:
{
"data": {
"product": {
"id": "1",
"name": "Green Kayak",
"category": "Watersports",
"price": 290
}
}
}
使用不依赖输入类型的突变(如
storeSupplier
)的过程类似。以下是使用
storeSupplier
突变的示例:
mutation {
storeSupplier(
name: "AcmeCo"
city: "Chicago"
products: [1, 3]
) {
id
name
city
products {
name
}
}
}
执行该查询后,将创建一个新的供应商,并显示以下结果:
{
"data": {
"storeSupplier": {
"id": "100",
"name": "AcmeCo",
"city": "Chicago",
"products": [
{ "name": "Green Kayak" },
{ "name": "Soccer Ball" }
]
}
}
}
5. 其他 GraphQL 特性
5.1 使用请求变量
GraphQL 变量允许一次性定义请求,然后每次使用时通过参数进行定制,避免客户端为每个操作动态生成和序列化完整的请求数据。以下是一个使用变量的查询示例:
query ($id: ID!) {
product(id: $id) {
id
name
category
price
}
}
变量在查询或突变中应用,名称以美元符号开头,并指定类型。在上述示例中,查询定义了一个名为
id
的变量,类型为必需的
ID
。在查询内部,变量作为
$id
使用,并传递给
product
查询的参数。
要使用该变量,将查询输入到 GraphiQL 中,展开窗口左下角的
Query Variables
部分,并输入以下代码:
{
"id": 2
}
这为
id
变量提供了值 2。点击
Execute Query
按钮,查询和变量将被发送到 GraphQL 服务器,结果将选择
id
为 2 的产品对象。
变量在使用 GraphiQL 时可能不太明显,但在客户端开发中可以简化开发过程。
5.2 进行多个请求
单个操作可以包含多个请求或突变。以下是在 GraphiQL 窗口中输入多个查询的示例:
query {
product(id: 1) {
id
name
category
price
},
supplier(id: 1) {
id
name
city
}
}
查询之间用逗号分隔,包含在
query
关键字后的外层大括号内。点击
Execute Query
按钮,将看到将两个查询结果合并为一个响应的输出:
{
"data": {
"product": {
"id": "1",
"name": "Kayak",
"category": "Watersports",
"price": 275
},
"supplier": {
"id": "1",
"name": "Surf Dudes",
"city": "San Jose"
}
}
}
每个查询的名称用于表示响应中的对应部分,便于区分不同查询的结果。当需要多次使用相同的查询时,GraphQL 支持别名,为结果分配一个名称。以下是使用查询别名的示例:
query {
first: product(id: 1) {
id
name
category
price
},
second: product(id: 2) {
id
name
category
price
}
}
执行该查询后,将看到别名在查询结果中的使用:
{
"data": {
"first": {
"id": "1",
"name": "Kayak",
"category": "Watersports",
"price": 275
},
"second": {
"id": "2",
"name": "Lifejacket",
"category": "Watersports",
"price": 48.95
}
}
}
5.3 使用查询片段进行字段选择
每个查询都需要选择结果字段,这可能导致客户端代码重复。GraphQL 片段功能允许一次性定义字段选择,然后应用于多个请求。以下是定义和使用查询片段的示例:
fragment coreFields on product {
id
name
category
}
query {
first: product(id: 1) {
...coreFields
price
},
second: product(id: 2) {
...coreFields
}
}
片段使用
fragment
和
on
关键字定义,特定于单个类型。在上述示例中,片段
coreFields
为
product
对象定义。使用扩展运算符
...
应用片段,可以与常规字段选择混合使用。
执行该查询后,将看到以下结果:
{
"data": {
"first": {
"id": "1",
"name": "Kayak",
"category": "Watersports",
"price": 275
},
"second": {
"id": "2",
"name": "Lifejacket",
"category": "Watersports"
}
}
}
6. 消费 GraphQL
可以通过多种方式在 React 应用程序中消费 GraphQL 服务,包括直接使用 HTTP 请求、将 GraphQL 与数据存储集成以及使用专用的 GraphQL 客户端。
6.1 为项目添加包
为了在 React 应用程序中直接接收 GraphQL 数据,需要添加一些额外的包。打开新的命令提示符,导航到项目文件夹并运行以下命令:
npm install apollo-boost@0.1.22
npm install react-apollo@2.3.2
以下是这些新包的用途说明:
| 名称 | 描述 |
| ---- | ---- |
| apollo-boost | 该包包含 Apollo GraphQL 客户端,其配置适用于大多数项目。 |
| react-apollo | 该包包含 Apollo 客户端的 React 集成。 |
通过这些步骤,你可以在 React 应用程序中开始使用 GraphQL 服务,享受其灵活的数据查询和操作能力。
GraphQL 入门与应用指南
7. 不同消费 GraphQL 的方式流程分析
在 React 应用中消费 GraphQL 服务,有不同的方式,下面为你详细分析其流程。
7.1 直接使用 HTTP 请求
-
步骤 1:构建请求
确定 GraphQL 查询或突变的内容,将其作为请求体的一部分。例如,若要查询product信息,请求体可能如下:
{
"query": "query { product(id: 1) { id name category price } }"
}
-
步骤 2:设置请求头
设置请求头的Content-Type为application/json,以表明请求体是 JSON 格式。 -
步骤 3:发送请求
使用fetch或其他 HTTP 库发送 POST 请求到 GraphQL 服务器的端点。示例代码如下:
fetch('https://your-graphql-server.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"query": "query { product(id: 1) { id name category price } }"
})
})
.then(response => response.json())
.then(data => console.log(data));
7.2 将 GraphQL 与数据存储集成
graph LR
A[定义 GraphQL 查询] --> B[发送查询到服务器]
B --> C[接收服务器响应]
C --> D[解析响应数据]
D --> E[将数据存入数据存储]
E --> F[从数据存储获取数据用于渲染]
-
步骤 1:定义 GraphQL 查询
根据应用需求,定义合适的查询或突变。 -
步骤 2:发送查询到服务器
与直接使用 HTTP 请求的发送方式类似,将查询发送到 GraphQL 服务器。 -
步骤 3:接收服务器响应
获取服务器返回的 JSON 数据。 -
步骤 4:解析响应数据
提取所需的数据字段。 -
步骤 5:将数据存入数据存储
可以使用 Redux、MobX 等数据存储库,将解析后的数据存入其中。 -
步骤 6:从数据存储获取数据用于渲染
在 React 组件中,从数据存储中获取数据并进行渲染。
7.3 使用专用的 GraphQL 客户端
以 Apollo 客户端为例,其使用流程如下:
graph LR
A[安装 Apollo 相关包] --> B[创建 Apollo 客户端实例]
B --> C[配置客户端连接到 GraphQL 服务器]
C --> D[在 React 应用中使用 Apollo 组件或钩子]
D --> E[执行查询或突变]
E --> F[获取并处理数据]
-
步骤 1:安装 Apollo 相关包
运行以下命令安装所需包:
npm install apollo-boost@0.1.22
npm install react-apollo@2.3.2
- 步骤 2:创建 Apollo 客户端实例
import ApolloClient from 'apollo-boost';
const client = new ApolloClient({
uri: 'https://your-graphql-server.com/graphql'
});
-
步骤 3:配置客户端连接到 GraphQL 服务器
在创建实例时,通过uri参数指定服务器的端点。 -
步骤 4:在 React 应用中使用 Apollo 组件或钩子
例如,使用useQuery钩子进行查询:
import { useQuery, gql } from '@apollo/client';
const GET_PRODUCT = gql`
query {
product(id: 1) {
id
name
category
price
}
}
`;
function ProductComponent() {
const { loading, error, data } = useQuery(GET_PRODUCT);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<p>ID: {data.product.id}</p>
<p>Name: {data.product.name}</p>
<p>Category: {data.product.category}</p>
<p>Price: {data.product.price}</p>
</div>
);
}
-
步骤 5:执行查询或突变
调用相应的钩子或组件执行查询或突变操作。 -
步骤 6:获取并处理数据
根据返回的状态(如loading、error、data)进行相应的处理和渲染。
8. 总结
GraphQL 作为一种强大的数据查询和操作语言,为前端开发带来了诸多便利。通过本文,我们详细了解了 GraphQL 的基础查询、带参数查询、字段参数、突变操作等核心概念,以及如何使用请求变量、进行多个请求和利用查询片段等高级特性。同时,还探讨了在 React 应用中消费 GraphQL 服务的不同方式,包括直接使用 HTTP 请求、与数据存储集成和使用专用的 GraphQL 客户端。
在实际开发中,可根据项目的具体需求和复杂度选择合适的消费方式。对于简单项目,直接使用 HTTP 请求可能足够;而对于大型项目,使用专用的 GraphQL 客户端(如 Apollo)能更好地管理数据和状态。希望这些知识能帮助你在开发中更高效地使用 GraphQL,提升应用的性能和开发效率。
超级会员免费看
439

被折叠的 条评论
为什么被折叠?



