51、GraphQL 入门与应用指南

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,提升应用的性能和开发效率。

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究”展开,提出了一种结合数据驱动方法Koopman算子理论的递归神经网络(RNN)模型线性化方法,旨在提升纳米定位系统的预测控制精度动态响应能力。研究通过构建数据驱动的线性化模型,克服了传统非线性系统建模复杂、计算开销大的问题,并在Matlab平台上实现了完整的算法仿真验证,展示了该方法在高精度定位控制中的有效性实用性。; 适合人群:具备一定自动化、控制理论或机器学习背景的科研人员工程技术人员,尤其是从事精密定位、智能控制、非线性系统建模预测控制相关领域的研究生研究人员。; 使用场景及目标:①应用于纳米级精密定位系统(如原子力显微镜、半导体制造设备)中的高性能预测控制;②为复杂非线性系统的数据驱动建模线性化提供新思路;③结合深度学习经典控制理论,推动智能控制算法的实际落地。; 阅读建议:建议读者结合Matlab代码实现部分,深入理解Koopman算子RNN结合的建模范式,重点关注数据预处理、模型训练控制系统集成等关键环节,并可通过替换实际系统数据进行迁移验证,以掌握该方法的核心思想工程应用技巧。
基于粒子群算法优化Kmeans聚类的居民用电行为分析研究(Matlb代码实现)内容概要:本文围绕基于粒子群算法(PSO)优化Kmeans聚类的居民用电行为分析展开研究,提出了一种结合智能优化算法传统聚类方法的技术路径。通过使用粒子群算法优化Kmeans聚类的初始聚类中心,有效克服了传统Kmeans算法易陷入局部最优、对初始值敏感的问题,提升了聚类的稳定性和准确性。研究利用Matlab实现了该算法,并应用于居民用电数据的行为模式识别分类,有助于精细化电力需求管理、用户画像构建及个性化用电服务设计。文档还提及相关应用场景如负荷预测、电力系统优化等,并提供了配套代码资源。; 适合人群:具备一定Matlab编程基础,从事电力系统、智能优化算法、数据分析等相关领域的研究人员或工程技术人员,尤其适合研究生及科研人员。; 使用场景及目标:①用于居民用电行为的高效聚类分析,挖掘典型用电模式;②提升Kmeans聚类算法的性能,避免局部最优问题;③为电力公司开展需求响应、负荷预测和用户分群管理提供技术支持;④作为智能优化算法机器学习结合应用的教学科研案例。; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,深入理解PSO优化Kmeans的核心机制,关注参数设置对聚类效果的影响,并尝试将其应用于其他相似的数据聚类问题中,以加深理解和拓展应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值