A GraphQL használata a Redux alkalmazásban

Az adatok lekérése és kezelése a Redux-ban túl sok munkát igényel. Ahogy Sashko Stubailo rámutat:

Sajnos a szerveradatok aszinkron módon történő betöltésének mintái a Redux alkalmazásban nem annyira megalapozottak, és gyakran külső segédkönyvtárak, például a redux-saga használatával járnak. Egyéni kódot kell írnia a kiszolgáló végpontjainak meghívásához, az adatok értelmezéséhez, normalizálásához és az áruházba való behelyezéshez - mindezt a különböző hiba- és betöltési állapotok nyomon követése mellett.

Az oktatóanyag végére megtanulta, hogyan oldja meg ezt a problémát azáltal, hogy hagyja, hogy az Apollo kliens lekérje és kezelje az Ön számára adatokat. Többé nem kell több műveletet diszpécsereket, reduktorokat és normalizálókat írni, hogy adatokat lehessen előhívni és szinkronizálni a kezelőfelület és a hátlap között.

Az oktatóanyag megkezdése előtt azonban győződjön meg arról, hogy

  • Ismeri a GraphQL-lekérdezések alapjait - ha Ön még teljesen új a GraphQL-ben, akkor az oktatóanyag elvégzése után érdemes visszatérnie.
  • Van némi tapasztalata a React / Redux-szel való együttműködésről - ha nem, akkor vissza kell térnie, miután elvégezte a reakció-bemutatót és a redux-oktatóanyagot.

Ebben az oktatóanyagban 6 szakaszon megyünk keresztül együtt.

  1. Szerver környezet beállítása (gyors)
  2. Redux kazán alkalmazás beállítása
  3. GraphQL kliens (Apollo kliens) hozzáadása
  4. Adatok lekérése GraphQL lekérdezéssel
  5. Még több adat beolvasása
  6. Következő lépések

1. Szerver környezet beállítása

Először is szükségünk van egy GraphQL szerverre. A futó szerver létrehozásának legegyszerűbb módja ennek a félelmetes oktatóanyagnak a kitöltése.

Ha lustának érzed magad, akkor egyszerűen klónozhatod a repót, amely majdnem ugyanaz a szerver, mint amit te kapnál, ha te magad csinálnád az oktatóanyagot. A kiszolgáló támogatja a GraphQL lekérdezéseket az adatok lekérésére egy SQLite adatbázisból.

Futtassuk és nézzük meg, hogy megfelelően működik-e:

$ git clone //github.com/woniesong92/apollo-starter-kit$ cd apollo-starter-kit$ npm install$ npm start

A szervernek a // localhost: 8080 / graphql címen kell futnia. Navigáljon arra az oldalra, és ellenőrizze, hogy működik-e egy működő GraphiQL felület az ilyen eredményekkel:

A GraphiQL segítségével tesztelheti a különböző lekérdezéseket, és azonnal megnézheti, hogy milyen választ kap a szervertől. Ha nem akarunk egy szerző vezetéknevét és egy vagyoni sütiüzenetet válaszolni, akkor az alábbi módon frissíthetjük a lekérdezést:

És pontosan így szeretjük. Megerősítettük, hogy szerverünk jól működik és jó válaszokat ad, ezért kezdjük el az ügyfél felépítését.

2. A redux kazán alkalmazás beállítása

Az egyszerűség kedvéért egy redux kazánt fogunk használni, hogy az összes beállítást (pl. Babel, webpack, CSS stb.) Ingyen megszerezhessük. Tetszik ez a kazán, mert a beállítása könnyen követhető és csak kliens oldali - ami tökéletesvé teszi ezt az oktatóanyagot.

$ git clone //github.com/woniesong92/react-redux-starter-kit.git$ cd react-redux-starter-kit$ npm install$ npm start

Keresse meg a // localhost: 3000 / címet, és nézze meg, hogy fut-e az ügyfélkiszolgáló.

Hurrá! Az ügyfél fut. Itt az ideje, hogy elkezdjünk hozzáadni egy GraphQL klienst. Ismét az a célunk, hogy a GraphQL-lekérdezések segítségével könnyedén lehívjuk az adatokat a szerverről, és sok erőfeszítés nélkül megjelenítsük azokat a céloldalon (HomeView).

3. GraphQL kliens (Apollo kliens) hozzáadása

Telepítse az apollo-client, react-apollo és graphql-tag csomagokat.

$ npm install apollo-client react-apollo graphql-tag --save

Ezután nyissa meg az src / container / AppContainer.js fájlt, a Redux alkalmazás gyökerét. Itt adjuk át a redux tárolót a gyermekkomponenseknek, a Reader-redux szolgáltató segítségével.

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 ( ) }}
export default AppContainer

Inicializálnunk kell egy ApolloClient szolgáltatót, és a Reader-redux szolgáltatóját le kell cserélnünk a react-apollo ApolloProvider szolgáltatására.

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('//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 ( ) }}
export default AppContainer

Ez az! Most egy GraphQL klienst adtunk hozzá egy sima Redux alkalmazáshoz, olyan könnyen.

Menjünk előre, és próbáljuk ki az első GraphQL lekérdezést.

4. Adatok lekérése GraphQL lekérdezésekkel

Nyissa meg az src / views / HomeView.js fájlt

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 ( 

Hello World

) }}
// 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)

A HomeView egy hagyományos Redux konténer (intelligens alkatrész). Ahhoz, hogy GraphQL-lekérdezéseket használjunk művelet-diszpécserek helyett az adatok lekéréséhez, együtt néhány változtatást fogunk végrehajtani.

  1. Távolítsa el teljesen a mapDispatchToProps () és a mapStateToProps () elemeket.
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 ( 

Hello World

) }}
export default connect({
})(HomeView)

2. Adja hozzá a mapQueriesToProps () elemet, és definiáljon egy GraphQL lekérdezést, amely lekéri a szerző információkat. Figyelje meg, hogy ez pontosan ugyanaz a lekérdezés, amelyet az elején teszteltünk a kiszolgálón található GraphIQL felület használatával.

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 ( 

Hello World

) }}
// 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. Cserélje ki a connect-t a reakció-redux-ról a connect-ről a reakció-apollo-ra, és adja át argumentumként a mapQueriesToProps-ot. Miután a mapQueriesToProps csatlakozik az ApolloClient-hez, a lekérdezés automatikusan lekéri az adatokat a háttérprogramból, amikor a HomeView megjelenik, és az adatokat továbbítja a kellékeken keresztül.

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 ( 

Hello World

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

4. Adja meg a kellékekből átadott adatokat:

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 

Loading

}
 return ( 

{author.firstName}'s posts

{author.posts && author.posts.map((post, idx) => (
  • {post.title}
  • ))} ) }}
    const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { author(firstName:"Edmond", lastName: "Jones"){ firstName posts { title } } } ` } }}
    export default connect({ mapQueriesToProps})(HomeView)

    If all went well, your rendered HomeView should look like below:

    To fetch and render the data we wanted, we didn’t have to write any action dispatcher, reducer, or normalizer. All we had to do on the client was to write a single GraphQL query!

    We successfully achieved our initial goal. But that query was quite simple. What if we wanted to display all authors instead of just one author?

    5. Fetching even more data

    In order to fetch and display all authors, we have to update our GraphQL query and render method:

    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 

    Loading

    }
     return ( {authors.map((author, idx) => ( 

    {author.firstName}'s posts

    {author.posts && author.posts.map((post, idx) => (
  • {post.title}
  • ))} ))} ) }}
    const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { authors { firstName posts { title } } } ` } }}
    export default connect({ mapQueriesToProps})(HomeView)

    However, once you refresh your browser HomeView page, you will notice that you have an error in your console:

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

    Ah, right! In our GraphQL server, we didn’t really define how to fetch authors.

    Let’s go back to our server and see what we have. Open the file apollo-starter-kit/data/resolvers.js

    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;

    Looking at Query resolver, we notice that our GraphQL server only understands author and getFortuneCookie queries now. We should teach it how to “resolve” the query authors.

    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;

    We are not done yet. Open the file apollo-starter-kit/data/schema.js

    const typeDefinitions = `...
    type Query { author(firstName: String, lastName: String): Author getFortuneCookie: String}schema { query: Query}`;
    export default [typeDefinitions];

    This Schema makes it clear what kind of queries the server should expect. It doesn’t expect authors query yet so let’s update it.

    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];

    Now that our GraphQL server knows what the “authors” query means, let’s go back to our client. We already updated our query so we don’t have to touch anything.

    export class HomeView extends React.Component {
    ...
    const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { authors { firstName posts { title } } } ` } }}
    export default connect({ mapQueriesToProps})(HomeView)

    With this query we expect to get all authors with their first names and posts. Go ahead and refresh the browser to see if we are getting the right data.

    If everything went well, your HomeView page will look like above.

    6. Next steps

    This tutorial only explores a small part of GraphQL and leaves out a lot of concepts such as updating data on the server or using a different backend server (e.g. Rails).

    While I work to introduce these in subsequent tutorials, you can read Sashko’s post or the Apollo Client Doc to better understand what’s going on under the hood (for example, what happened when we replaced Provider with ApolloProvider?).

    Digging into the source code of GitHunt, a full-stack Apollo Client and Server example app, also seems a great way to learn.

    If you have feedback, please leave it in the comment. I will try my best to be helpful :)