在 redux 应用中使用 GraphQL

2019-03-28 10:13:19 浏览数 (1)

在 Redux 应用中获取和管理数据需要做许多工作。正如 Sashko Stubailo 指出的:

不幸的是,在 Redux 应用程序中异步加载服务器数据的模式还没有建立起来,并且经常需要使用外部帮助程序库,如 redux-saga。 您需要编写自定义代码来调用服务器接口,解释数据,对其进行规范化并将其插入到存储中 - 同时跟踪各种错误和加载状态。

在本教程中,您将学习如何通过 Apollo Client 来获取和管理数据。 您将不再需要编写多个操作调度程序、reducer 和规范化程序来在前端和后端之间获取并同步数据。

在开始本教程之前,请确保:

  • 了解基本的 GraphQL 查询——如果 GraphQL 对您来说完全是陌生的,您需要先学习此教程。
  • 有一定的 React/Redux 经验——否则,请先阅读 react 教程和 redux 教程

在本教程中,我们将学习以下6个小节。

  1. 快速的启动一个服务端环境
  2. 启动一个 redux 脚手架
  3. 增加一个 GraphQL 客户端 (Apollo Client)
  4. 使用 GraphQL 获取数据
  5. 获取更多的数据
  6. 接下来要做的
1. 启动一个服务端环境

首先,我们需要一个 GraphQL 服务器。最简单的方式就是完成这一教程.

如果你不想这么麻烦的话,可以克隆我的 repo, 这个项目和上述教程几乎是一模一样的。我们启动的服务器支持从一个 SQLite 数据库中进行 GraphQL 查询。

让我们启动这个服务器:

代码语言:javascript复制
$ git clone [https://github.com/woniesong92/apollo-starter-kit](https://github.com/woniesong92/apollo-starter-kit)
$ cd apollo-starter-kit
$ npm install
$ npm start

服务器可以通过 http://localhost:8080/graphql 访问。 在该页您可以看到一个如下 GraphQL 界面:

GraphiQL 允许您测试不同的查询,并立即看到从服务器获得的响应。 如果我们不想在响应中看到作者的姓氏和幸运饼干签语条,可以更新成以下查询:

可以看到,这正是我们想要的形式。现在,我们已经确认服务器运行正常,并返回正确的响应,接下来让我们开始构建客户端。

2. 启动 redux 脚手架应用

为了简单起见,我们直接应用一个 redux 脚手架,这样我们就直接获得了一整套开发工具 (比如 Babel, webpack, CSS 等等) 。

代码语言:javascript复制
$ git clone [https://github.com/woniesong92/react-redux-starter-kit.git](https://github.com/woniesong92/react-redux-starter-kit.git)
$ cd react-redux-starter-kit
$ npm install
$ npm start

在浏览器中打开 http://localhost:3000/ :

好极了! 客户端正在运行,现在是开始添加 GraphQL 客户端的时候了。我们的目标是使用 GraphQL 查询,从服务器轻松获取数据并将其呈现在着陆页(HomeView)中。

3. 增加一个 GraphQL 客户端(Apollo 客户端)

安装 apollo-client, react-apollo, 和 graphql-tag。

代码语言:javascript复制
$ npm install apollo-client react-apollo graphql-tag --save

打开 Redux 应用的入口文件 src/containers/AppContainer.js。在这里我们把 redux store 通过 react-redux 中的 provider 传递给子组件。

代码语言:javascript复制
import React, { PropTypes } from 'react'
import { Router } from 'react-router'
import { Provider } from 'react-redux'

class AppContainer extends React.Component {
  static propTypes = {
    history: PropTypes.object.isRequired,
    routes: PropTypes.object.isRequired,
    routerKey: PropTypes.number,
    store: PropTypes.object.isRequired
  }

  render () {
    const { history, routes, routerKey, store } = this.props
    return (
      <Provider store={store}>
        <div>
          <Router history={history} children={routes} key={routerKey} />
        </div>
      </Provider>
    )
  }
}

export default AppContainer

首先,我们需要初始化一个 ApolloClient,并将 react-redux 中的 Provider 替换为来自 react-apollo 的 ApolloProvider。

代码语言:javascript复制
import React, { Component, PropTypes } from 'react'
import { Router } from 'react-router'
import ApolloClient, { createNetworkInterface, addTypename } from 'apollo-client'
import { ApolloProvider } from 'react-apollo'

const client = new ApolloClient({
  networkInterface: createNetworkInterface('[http://localhost:8080/graphql'](http://localhost:8080/graphql')),
  queryTransformer: addTypename,
})

class AppContainer extends Component {
  static propTypes = {
    history: PropTypes.object.isRequired,
    routes: PropTypes.object.isRequired,
    store: PropTypes.object.isRequired
  }

  render () {
    const { history, routes } = this.props
    return (
      <ApolloProvider client={client}>
        <div>
          <Router history={history} children={routes} />
        </div>
      </ApolloProvider>
    )
  }
}

export default AppContainer

好了!这样我们就把一个 GraphQL 客户端添加进了一个普通的 Redux 应用中。 接下来让我们开始第一个 GraphQL 查询吧。

4. 通过 GraphQL 查询获取数据

进入 src/views/HomeView.js

代码语言:javascript复制
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

export class HomeView extends React.Component {
  constructor(props) {
    super(props)
  }

  render () {
    return (
      <div className='home'>
        <h1>Hello World</h1>
      </div>
    )
  }
}

// This is where you usually retrieve the data stored in the redux store (e.g posts: state.posts.data)
const mapStateToProps = (state, { params }) => ({

})

// This is where you usually bind dispatch to actions that are used to request data from the backend. You will call the dispatcher in componentDidMount.
const mapDispatchToProps = (dispatch) => {
  const actions = {}

  return {
    actions: bindActionCreators(actions, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(HomeView)

HomeView 是一个典型的 Redux 容器组件(smart 组件)。如果想要使用 GraphQL 查询语句而不是 action dispatchers 来获取数据,需要做以下改变:

1. 移除 mapDispatchToProps() 和 mapStateToProps()

代码语言:javascript复制
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

export class HomeView extends React.Component {
  constructor(props) {
    super(props)
  }

  render () {
    return (
      <div className='home'>
        <h1>Hello World</h1>
      </div>
    )
  }
}

export default connect({

})(HomeView)

2. 增加 mapQueriesToProps() 并且定义一个能够获取作者信息的 GraphQL 查询。注意,这个查询语句其实和我们之前在 GraphIQL 界面上测试的语句是一样的。

代码语言:javascript复制
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

export class HomeView extends React.Component {
  constructor(props) {
    super(props)
  }

  render () {
    return (
      <div className='home'>
        <h1>Hello World</h1>
      </div>
    )
  }
}

// NOTE: This will be automatically fired when the component is rendered, sending this exact GraphQL query to the backend.
const mapQueriesToProps = ({ ownProps, state }) => {
  return {
    data: {
      query: gql
        query {
          author(firstName:"Edmond", lastName: "Jones"){
            firstName
            posts {
              title
            }
          }
        }
      
    }
  }
}

export default connect({

})(HomeView)

3. 将从 react-redux 中导出的 connect 方法替换成从 react-apollo 中导出的 connect 方法,同时将 mapQueriesToProps 作为参数传入。在 mapQueriesToProps 连接到 ApolloClient 之后,查询语句将会在 HomeView 被渲染时自动从后端获取数据,然后通过 props 将数据传递下去。

代码语言:javascript复制
import React from 'react'
import { connect } from 'react-apollo' // NOTE: different connect!
import gql from 'graphql-tag' // NOTE: lets us define GraphQL queries in a template language

export class HomeView extends React.Component {
  constructor(props) {
    super(props)
  }

render () {
    return (
      <div className='home'>
        <h1>Hello World</h1>
      </div>
    )
  }
}

const mapQueriesToProps = ({ ownProps, state }) => {
  return {
    data: {
      query: gql
        query {
          author(firstName:"Edmond", lastName: "Jones"){
            firstName
            posts {
              title
            }
          }
        }
      
    }
  }
}

export default connect({
  mapQueriesToProps
})(HomeView)

4. 渲染从 props 中传递下来的数据:

代码语言:javascript复制
import React from 'react'
import { connect } from 'react-apollo' // NOTE: different connect!
import gql from 'graphql-tag' // NOTE: lets us define GraphQL queries in a template language

export class HomeView extends React.Component {
  constructor(props) {
    super(props)
  }

  render () {
    const author = this.props.data.author
    if (!author) {
      return <h1>Loading</h1>
    }

    return (
      <div>
        <h1>{author.firstName}'s posts</h1>
        {author.posts && author.posts.map((post, idx) => (
          <li key={idx}>{post.title}</li>
        ))}
      </div>
    )
  }
}

const mapQueriesToProps = ({ ownProps, state }) => {
  return {
    data: {
      query: gql
        query {
          author(firstName:"Edmond", lastName: "Jones"){
            firstName
            posts {
              title
            }
          }
        }
      
    }
  }
}

export default connect({
  mapQueriesToProps
})(HomeView)

如果进展顺利的话,得到的 HomeView 应该如下所示:

从上面的例子可以看出,如果我们想要获取并渲染数据,我们并不需要写任何的 action dispatcher, reduer, 或者数据规范化方法。我们只需要在客户端中写一条 GraphQL 查询语句!

我们已经达成了目的。不过这个查询语句还是太简单了,如果我们想要显示所有的作者应该怎么做呢?

5. 获取更多的数据

为了获取并显示所有的作者信息,我们需要更新 GraphQL 查询语句以及渲染函数:

代码语言:javascript复制
import React from 'react'
import { connect } from 'react-apollo' // NOTE: different connect!
import gql from 'graphql-tag' // NOTE: lets us define GraphQL queries in a template language

export class HomeView extends React.Component {
  constructor(props) {
    super(props)
  }

render () {
    const authors = this.props.data.authors
    if (!authors) {
      return <h1>Loading</h1>
    }

    return (
      <div>
        {authors.map((author, idx) => (
          <div key={'author-' idx}>
            <h1>{author.firstName}'s posts</h1>
            {author.posts && author.posts.map((post, idx) => (
              <li key={idx}>{post.title}</li>
            ))}
          </div>
        ))}
      </div>
    )
  }
}

const mapQueriesToProps = ({ ownProps, state }) => {
  return {
    data: {
      query: gql
        query {
          authors {
            firstName
            posts {
              title
            }
          }
        }
      
    }
  }
}

export default connect({
  mapQueriesToProps
})(HomeView)

然而,一旦你刷新页面,你将发现在 console 中会出现这么一条错误:

ApolloError {graphQLErrors: Array[1], networkError: undefined, message: “GraphQL error: Cannot query field “authors” on type “Query”. Did you mean “author”?”}

对了!在我们的 GraphQL 服务器中,并没有定义如何获取 authors

现在让我们回到服务端,打开 apollo-starter-kit/data/resolvers.js 文件:

代码语言:javascript复制
import { Author, FortuneCookie } from './connectors';

const resolvers = {
  Query: {
    author(_, args) {
      return Author.find({ where: args });
    },
    getFortuneCookie() {
      return FortuneCookie.getOne()
    }
  },
  Author: {
    posts(author) {
      return author.getPosts();
    },
  },
  Post: {
    author(post) {
      return post.getAuthor();
    },
  },
};

export default resolvers;

从查询语句的处理函数中可以看出,现在我们的 GraphQL 服务器只能理解 authorgetFortuenCookie 语句。现在我们要教会它处理 authors 查询。

代码语言:javascript复制
import { Author, FortuneCookie } from './connectors';

const resolvers = {
  Query: {
    author(_, args) {
      return Author.find({ where: args });
    },
    getFortuneCookie() {
      return FortuneCookie.getOne()
    },
    authors() { // the query "authors" means returning all authors!
      return Author.findAll({})
    }
  },
  ...
};

export default resolvers;

还没结束,打开 apollo-starter-kit/data/schema.js

代码语言:javascript复制
const typeDefinitions = 
...
type Query {
  author(firstName: String, lastName: String): Author
  getFortuneCookie: String
}
schema {
  query: Query
}
;

export default [typeDefinitions];

这个 Schema 表明了哪些查询语句是服务器所接受的。它暂时不接受 authors 查询,现在让我们更新一下。

代码语言:javascript复制
const typeDefinitions = 
...

type Query {
  author(firstName: String, lastName: String): Author
  getFortuneCookie: String,
  authors: [Author] // 'authors' query should return an array of 
                    // Author
}
schema {
  query: Query
}
;

export default [typeDefinitions];

现在既然我们的 GraphQL 服务器知道了 "authors" 查询的含义,让我们回到客户端。因为我们已经更新了查询语句,所以其实不用改变任何东西。

代码语言:javascript复制
export class HomeView extends React.Component {
...

const mapQueriesToProps = ({ ownProps, state }) => {
  return {
    data: {
      query: gql
        query {
          authors {
            firstName
            posts {
              title
            }
          }
        }
      
    }
  }
}

export default connect({
  mapQueriesToProps
})(HomeView)

通过这条查询语句我们就可以得到所有的作者姓氏以及他们的作品列表。接下来让我们刷新一下浏览器看看是否得到了正确的数据。

如果一切顺利的话,你的 HomeView 将会看起来像这个样子。

6. 接下来

这篇教程只探索了 GraphQL 的一小部分,还有许多内容尚未涉及,比如在服务端更新数据或者使用其他的服务端(如 Rails)。

不过我准备在后续的教程中介绍这些内容,您可以阅读 Sashko 的 文章 或者 Apollo Client 文档来更好的理解 GraphQL 的原理 (比如,当我们把 Provider 替换成 ApolloProvider 时到底发生了什么?).

深入研究 GitHunt 的源码也是一种很好的学习方式,这是一个全栈的 Apollo 客户端及服务端 app 样例。

来源:http://www.zcfy.cc/article/how-to-use-graphql-in-your-redux-app-freecodecamp-4080.html

0 人点赞