53、使用 GraphQL 客户端框架消费 GraphQL

使用 GraphQL 客户端框架消费 GraphQL

在现代 Web 开发中,GraphQL 作为一种强大的数据查询语言,为前端开发者提供了更加灵活和高效的数据获取方式。本文将详细介绍如何使用 Apollo Client 这个流行的 GraphQL 客户端框架来消费 GraphQL 数据,包括客户端配置、组件创建、数据查询、突变操作以及缓存更新等内容。

1. 使用 GraphQL 客户端框架

在之前的开发中,我们展示了如何将 GraphQL 与 Redux 数据存储结合使用。不过,还有另一种方法,即使用一个可以替代数据存储的包,它能直接将 GraphQL 数据提供给需要的组件,同时对数据进行缓存,避免因孤立组件间的导航而导致的重复 HTTP 请求。

这里我们选择使用 Apollo Client,它是与 Apollo GraphQL 服务器由同一开发者开发的客户端包,并且可以与任何 GraphQL 服务器配合使用。其完整文档可在 https://www.apollographql.com/docs/react 查看。

2. 配置客户端

第一步是配置客户端,让它知道将 GraphQL 请求和突变发送到哪里。以下是在 App.js 文件中配置 Apollo Client 的代码:

import React, { Component } from "react";
// import { Provider } from "react-redux";
// import dataStore from "./store";
import { Selector } from "./Selector";
//import { PRODUCTS, SUPPLIERS } from "./store/dataTypes";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";

const client = new ApolloClient({
    uri: "http://localhost:3600/graphql"
});

export default class App extends Component {
    render() {
        return  <ApolloProvider client={ client }>
                    <Selector />
                </ApolloProvider>
    }
}

上述代码中,我们创建了一个新的 ApolloClient 对象来管理与 GraphQL 服务器的关系,其构造函数接受一个配置对象,其中 uri 属性指定了 GraphQL 请求的 URL。虽然还有其他配置选项,但默认设置适用于大多数项目。 ApolloProvider 组件用于将 GraphQL 功能集成到 React 中,并将 ApolloClient 对象赋值给 client 属性。

3. 创建 GraphQL 组件

接下来,我们要创建一个组件,作为 GraphQL 和用户界面之间的桥梁。这样,呈现给用户的内容将由不直接依赖特定数据机制(如 Redux 数据存储、RESTful Web 服务或 GraphQL 客户端)的组件生成。以下是 GraphQLTable.js 文件的内容:

import React, { Component } from "react";
import { Query } from "react-apollo";
import gql from "graphql-tag";
import * as queries from "./queries";
import { ProductTable } from "../ProductTable";

export const GraphQLTable = () => {
    const getAll = gql(queries.products.getAll.graphql);
    return class extends Component {
        constructor(props) {
            super(props);
            this.editCallback = (item) => this.props.history
                .push(`/products/edit/${item.id}`);
        }

        render() {
            return <Query query={ getAll }>
                {({loading, data, refetch }) => {
                    if (loading) {
                        return <h5
                            className="bg-info text-white text-center m-2 p-2">
                                Loading...
                        </h5>
                    } else {
                        return <React.Fragment>
                            <ProductTable products={data.products}
                                editCallback= { this.editCallback }
                                deleteCallback={ () => {} } />
                            <div className="text-center">
                                <button className="btn btn-primary"
                                        onClick={ () => refetch() }>
                                    Reload Data
                                </button>
                            </div>
                        </React.Fragment>
                    }
                }}
            </Query>
        }
    }
}

在这个组件中,我们使用 gql 函数处理 GraphQL 查询, Query 组件会在渲染时将查询发送到服务器,并通过渲染属性函数提供查询结果。以下是 Query 组件渲染属性对象的一些有用属性:
| 属性名 | 描述 |
| ---- | ---- |
| data | 返回查询产生的数据 |
| loading | 查询处理时返回 true |
| error | 返回任何查询错误的详细信息 |
| variables | 返回查询使用的变量 |
| refetch | 返回一个函数,可用于重新发送查询,可选择提供新变量 |

4. 应用 GraphQL 组件

我们需要更新路由配置,让 GraphQLTable 组件响应 /product URL。以下是 Selector.js 文件的修改内容:

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch, Redirect }
    from "react-router-dom";
import { ToggleLink } from "./routing/ToggleLink";
// import { RoutedDisplay } from "./routing/RoutedDisplay";
// import { IsolatedTable } from "./IsolatedTable";
// import { IsolatedEditor } from "./IsolatedEditor";
// import { RequestError } from "./webservice/RequestError";
import { GraphQLTable } from "./graphql/GraphQLTable";

export class Selector extends Component {
    render() {
        return <Router>
            <div className="container-fluid">
                <div className="row">
                    <div className="col-2">
                        <ToggleLink to="/products">Products</ToggleLink>
                    </div>
                    <div className="col">
                        <Switch>
                            <Route path="/products" exact={true}
                                component={ GraphQLTable()}  />
                            <Redirect to="/products" />
                        </Switch>
                    </div>
                </div>
            </div>
        </Router>
    }
}

保存更改后,你将看到 GraphQL 产品和供应商数据显示出来。如果遇到错误,可以停止开发工具并使用 npm start 重新启动,这将重置 GraphQL 服务器使用的数据。

5. 使用突变操作

Mutation 组件用于执行 GraphQL 突变操作。以下是在 GraphQLTable.js 文件中使用 Mutation 进行产品或供应商对象删除操作的代码:

import React, { Component } from "react";
import { Query, Mutation } from "react-apollo";
import gql from "graphql-tag";
import * as queries from "./queries";
import { ProductTable } from "../ProductTable";
import * as mutations from "./mutations";

export const GraphQLTable = () => {
    const getAll = gql(queries.products.getAll.graphql);
    const deleteItem = gql(mutations.products.delete.graphql);
    return class extends Component {
        constructor(props) {
            super(props);
            this.editCallback = (item) => this.props.history
                .push(`/products/edit/${item.id}`);
        }

        render() {
            return <Query query={ getAll }>
                {({loading, data, refetch }) => {
                    if (loading) {
                        return <h5
                            className="bg-info text-white text-center m-2 p-2">
                                Loading...
                        </h5>
                    } else {
                        return <Mutation mutation={ deleteItem }
                                refetchQueries={ () => [{query: getAll}]  }>
                            { doDelete =>
                                 <React.Fragment>
                                    <ProductTable products={data.products}
                                        editCallback= { this.editCallback }
                                        deleteCallback={ (p) =>
                                            doDelete({variables: {id: p.id} }) }  />
                                    <div className="text-center">
                                        <button className="btn btn-primary"
                                                onClick={ () => refetch() }>
                                            Reload Data
                                        </button>
                                    </div>
                                </React.Fragment>
                        }
                        </Mutation>
                    }
                }}
            </Query>
        }
    }
}

Mutation 组件的配置属性如下:
| 属性名 | 描述 |
| ---- | ---- |
| mutation | 指定要发送到服务器的突变 |
| variables | 指定突变的变量,也可在执行突变时提供 |
| refetchQueries | 指定突变完成后要执行的一个或多个查询 |
| update | 指定一个函数,用于在突变完成时更新缓存 |
| onCompleted | 指定一个回调函数,在突变完成时调用 |

6. 不通过查询更新缓存数据

对于一些简单操作,在突变后重新查询服务器获取新数据可能过于繁琐,因为应用程序确切知道突变的影响,可以直接将更改应用到缓存数据。以下是更新 GraphQLTable.js 文件以实现此功能的代码:

import React, { Component } from "react";
import { Query, Mutation } from "react-apollo";
import gql from "graphql-tag";
import * as queries from "./queries";
import { ProductTable } from "../ProductTable";
import * as mutations from "./mutations";

export const GraphQLTable = () => {
    const getAll = gql(queries.products.getAll.graphql);
    const deleteItem = gql(mutations.products.delete.graphql);
    return class extends Component {
        constructor(props) {
            super(props);
            this.editCallback = (item) => this.props.history
                .push(`/products/edit/${item.id}`);
        }

        removeItemFromCache(cache, mutationResult) {
            const deletedId =  mutationResult.data[mutations.products.delete.name];
            const data =
                cache.readQuery({ query: getAll })[queries.products.getAll.name];
            cache.writeQuery({
                query: getAll,
                data: { products: data.filter(item => item.id !== deletedId) }
            });
        }

        render() {
            return <Query query={ getAll }>
                {({loading, data, refetch }) => {
                    if (loading) {
                        return <h5
                            className="bg-info text-white text-center m-2 p-2">
                                Loading...
                        </h5>
                    } else {
                        return <Mutation mutation={ deleteItem }
                                update={ this.removeItemFromCache }>
                            { doDelete =>
                                 <React.Fragment>
                                    <ProductTable products={data.products}
                                        editCallback= { this.editCallback }
                                        deleteCallback={ (p) =>
                                            doDelete({variables: {id: p.id} }) }  />
                                    <div className="text-center">
                                        <button className="btn btn-primary"
                                                onClick={ () => refetch() }>
                                            Reload Data
                                        </button>
                                    </div>
                                </React.Fragment>
                        }
                        </Mutation>
                    }
                }}
            </Query>
        }
    }
}

这里使用 update 属性指定一个方法,在突变完成时调用该方法来更新缓存数据。Apollo Client 缓存的方法如下:
| 方法名 | 描述 |
| ---- | ---- |
| readQuery | 用于从与特定查询关联的缓存中读取数据。如果尝试读取不在缓存中的数据,将抛出错误 |
| writeQuery | 用于更新与特定查询关联的缓存中的数据 |

7. 添加对供应商数据和编辑的支持

为了支持产品和供应商数据,并引入数据编辑功能,我们对 GraphQLTable 组件进行了修改,使其能根据传入的数据类型动态选择查询、突变和要显示的组件。以下是修改后的 GraphQLTable.js 文件内容:

import React, { Component } from "react";
import { Query, Mutation } from "react-apollo";
import gql from "graphql-tag";
import * as queries from "./queries";
import { ProductTable } from "../ProductTable";
import * as mutations from "./mutations";
import { PRODUCTS, SUPPLIERS } from "../store/dataTypes";
import { SupplierTable } from "../SupplierTable";

export const GraphQLTable = (dataType) => {
    const getAll = gql(queries[dataType].getAll.graphql);
    const deleteItem = gql(mutations[dataType].delete.graphql);
    return class extends Component {
        constructor(props) {
            super(props);
            this.editCallback = (item) => this.props.history
                .push(`/${dataType}/edit/${item.id}`);
        }

        removeItemFromCache = (cache, mutationResult) => {
            const deletedId =  mutationResult.data[mutations[dataType].delete.name];
            const data =
                cache.readQuery({ query: getAll })[queries[dataType].getAll.name];
            cache.writeQuery({
                query: getAll,
                data: { [dataType]: data.filter(item => item.id !== deletedId) }
            });
        }

        getRefetchQueries() {
            return dataType === PRODUCTS
                ? [{query: gql(queries[SUPPLIERS].getAll.graphql)}] : []
        }

        render() {
            return <Query query={ getAll }>
                {({loading, data, refetch }) => {
                    if (loading) {
                        return <h5
                            className="bg-info text-white text-center m-2 p-2">
                                Loading...
                        </h5>
                    } else {
                        return <Mutation mutation={ deleteItem }
                                update={ this.removeItemFromCache }
                                refetchQueries={ this.getRefetchQueries }>
                            { doDelete =>
                                 <React.Fragment>
                                     { dataType === PRODUCTS &&
                                        <ProductTable products={data.products}
                                            editCallback= { this.editCallback }
                                            deleteCallback={ (p) =>
                                                doDelete({variables: {id: p.id} }) }
                                        />
                                    }
                                    { dataType === SUPPLIERS &&
                                        <SupplierTable suppliers={data.suppliers}
                                            editCallback= { this.editCallback }
                                            deleteCallback={ (p) =>
                                                doDelete({variables: {id: p.id} }) }
                                        />
                                    }
                                    <div className="text-center">
                                        <button className="btn btn-primary"
                                                onClick={ () => refetch() }>
                                            Reload Data
                                        </button>
                                    </div>
                                </React.Fragment>
                        }
                        </Mutation>
                    }
                }}
            </Query>
        }
    }
}
8. 创建编辑器组件

为了让用户能够编辑对象,我们创建了 GraphQLEditor.js 组件。以下是该文件的部分内容:

import React, { Component } from "react";
import gql from "graphql-tag";
import * as queries from "./queries";
import * as mutations from "./mutations";
import { Query, Mutation } from "react-apollo";
import { PRODUCTS } from "../store/dataTypes";
import { ProductEditor } from "../ProductEditor";
import { SupplierEditor } from "../SupplierEditor";

export const GraphQLEditor = () => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.dataType = this.props.match.params.dataType;
            this.id = this.props.match.params.id;
            this.query = gql(queries[this.dataType].getOne.graphql);
            this.variables = { id: this.id };
            this.mutation = gql(mutations[this.dataType].store.graphql);
            this.navigation = () => props.history.push(`/${this.dataType}`);
        }

        render() {
            return <Query query={ this.query} variables={ this.variables }>
            {
                ({loading, data}) => {
                    if (!loading) {
                        return <Mutation mutation={ this.mutation }
                                onCompleted={ this.navigation }>

这个组件通过 Query 组件获取要编辑的对象数据,然后使用 Mutation 组件处理数据的存储操作,并在操作完成后进行导航。

综上所述,通过使用 Apollo Client 客户端框架,我们可以方便地消费 GraphQL 数据,实现数据的查询、突变和缓存管理,同时支持多类型数据和编辑功能,为前端应用开发提供了强大的支持。

使用 GraphQL 客户端框架消费 GraphQL

9. 编辑器组件的完整实现

上一部分我们给出了 GraphQLEditor.js 组件的部分代码,下面是其完整实现:

import React, { Component } from "react";
import gql from "graphql-tag";
import * as queries from "./queries";
import * as mutations from "./mutations";
import { Query, Mutation } from "react-apollo";
import { PRODUCTS } from "../store/dataTypes";
import { ProductEditor } from "../ProductEditor";
import { SupplierEditor } from "../SupplierEditor";

export const GraphQLEditor = () => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.dataType = this.props.match.params.dataType;
            this.id = this.props.match.params.id;
            this.query = gql(queries[this.dataType].getOne.graphql);
            this.variables = { id: this.id };
            this.mutation = gql(mutations[this.dataType].store.graphql);
            this.navigation = () => this.props.history.push(`/${this.dataType}`);
        }

        render() {
            return <Query query={ this.query} variables={ this.variables }>
            {
                ({loading, data}) => {
                    if (!loading) {
                        return <Mutation mutation={ this.mutation }
                                onCompleted={ this.navigation }>
                            { doStore => {
                                const item = data[this.dataType];
                                return this.dataType === PRODUCTS
                                    ? <ProductEditor product={item}
                                                     storeCallback={(p) => doStore({ variables: p })} />
                                    : <SupplierEditor supplier={item}
                                                      storeCallback={(s) => doStore({ variables: s })} />;
                            }}
                        </Mutation>
                    } else {
                        return <h5 className="bg-info text-white text-center m-2 p-2">
                            Loading...
                        </h5>;
                    }
                }
            }
            </Query>
        }
    }
}

在这个完整的组件中, Query 组件负责获取要编辑的对象数据。当数据加载完成后, Mutation 组件会处理数据的存储操作。根据不同的数据类型(产品或供应商),会渲染不同的编辑器组件( ProductEditor SupplierEditor ),并将存储回调函数传递给它们。当存储操作完成后,会调用 navigation 方法进行页面导航。

10. 整体流程梳理

为了更清晰地理解整个使用 Apollo Client 消费 GraphQL 数据的流程,我们可以通过一个 mermaid 流程图来展示:

graph LR
    A[配置客户端] --> B[创建 GraphQL 组件]
    B --> C[应用 GraphQL 组件]
    C --> D[使用突变操作]
    D --> E[更新缓存数据]
    E --> F[添加多数据类型支持和编辑功能]
    F --> G[创建编辑器组件]
  • 配置客户端 :创建 ApolloClient 对象并设置 GraphQL 请求的 URL,使用 ApolloProvider 组件将其集成到 React 应用中。
  • 创建 GraphQL 组件 :使用 Query 组件发送查询请求,处理查询结果并渲染数据。
  • 应用 GraphQL 组件 :更新路由配置,让 GraphQLTable 组件响应特定 URL。
  • 使用突变操作 :使用 Mutation 组件执行 GraphQL 突变操作,如删除数据。
  • 更新缓存数据 :在突变操作完成后,根据情况选择重新查询服务器或直接更新缓存数据。
  • 添加多数据类型支持和编辑功能 :修改 GraphQLTable 组件,使其能根据数据类型动态选择查询、突变和显示组件。
  • 创建编辑器组件 :使用 Query 获取要编辑的数据,使用 Mutation 处理数据存储操作。
11. 注意事项
  • 避免数据冲突 :虽然 Apollo Client 和 Redux 可以在同一应用中使用,但要注意避免在 Redux 中存储由 Apollo Client 管理的数据,否则两者容易出现数据不一致的情况。
  • 函数调用方式 :在使用 refetch 函数时,要确保使用内联函数调用,避免将事件对象传递给 refetch 函数导致错误。例如:
<button className="btn btn-primary" onClick={ () => refetch() }>
    Reload Data
</button>
  • 错误处理 :如果在开发过程中遇到错误,可以停止开发工具并使用 npm start 重新启动,这有助于重置 GraphQL 服务器使用的数据。
12. 总结

通过本文的介绍,我们详细了解了如何使用 Apollo Client 客户端框架来消费 GraphQL 数据。从客户端的配置、组件的创建,到数据的查询、突变操作以及缓存的更新,再到支持多数据类型和编辑功能,我们逐步构建了一个完整的前端应用。

Apollo Client 提供了强大而灵活的功能,使得我们能够高效地与 GraphQL 服务器进行交互。它的缓存机制可以减少不必要的 HTTP 请求,提高应用的性能。同时,通过合理使用 Query Mutation 组件,我们可以方便地实现数据的展示、修改和删除等操作。

在实际开发中,我们可以根据具体的需求对代码进行进一步的优化和扩展。例如,可以添加更多的错误处理逻辑,提高用户体验;也可以对缓存更新策略进行调整,以适应不同的业务场景。希望本文能为你在使用 GraphQL 和 Apollo Client 进行前端开发时提供有价值的参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值