Sådan bruges GraphQL i din Redux-app

Hentning og styring af data i Redux kræver for meget arbejde. Som Sashko Stubailo påpeger:

Desværre er mønstrene til asynkron indlæsning af serverdata i en Redux-app ikke så veletablerede og involverer ofte brug af eksterne hjælperbiblioteker, som redux-saga. Du skal skrive brugerdefineret kode for at ringe til dine serverendepunkter, fortolke dataene, normalisere dem og indsætte dem i butikken - alt sammen mens du holder styr på forskellige fejl- og indlæsningstilstande.

Ved afslutningen af ​​denne vejledning har du lært, hvordan du løser dette problem ved at lade Apollo-klienten hente og administrere data til dig. Du bliver ikke længere nødt til at skrive flere handlingsdispatchere, reduceringsanordninger og normalisatorer for at hente og synkronisere data mellem din frontend og din backend.

Men inden du starter vejledningen, skal du sørge for at:

  • Du kender det grundlæggende i GraphQL-forespørgsler - hvis du er helt ny med GraphQL, skal du komme tilbage efter at have udført denne tutorial.
  • Du har en vis erfaring med at arbejde med React / Redux - hvis ikke, skal du komme tilbage efter at have udført react tutorial og redux tutorial.

I denne vejledning gennemgår vi 6 sektioner sammen.

  1. Opsætning af servermiljø (hurtig)
  2. Opsætning af redux kogepladeapp
  3. Tilføjelse af GraphQL-klient (Apollo Client)
  4. Henter data med GraphQL-forespørgsel
  5. Henter endnu flere data
  6. Næste skridt

1. Opsætning af servermiljø

For det første har vi brug for en GraphQL-server. Den nemmeste måde at have en kørende server på er at gennemføre denne fantastiske tutorial.

Hvis du føler dig doven, kan du bare klone min repo, som næsten er den samme server, som du ville få, hvis du selv lavede vejledningen. Serveren understøtter GraphQL-forespørgsler for at hente data fra en SQLite DB.

Lad os køre det og se om det fungerer korrekt:

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

Serveren skal køre på // localhost: 8080 / graphql. Naviger til den side og se om du får en fungerende GraphiQL-grænseflade med resultater som denne:

GraphiQL lader dig teste forskellige forespørgsler og straks se, hvilket svar du får fra serveren. Hvis vi ikke vil have en forfatters efternavn og en formue-cookie-besked i et svar, kan vi opdatere forespørgslen som nedenfor:

Og det er præcis sådan, vi kan lide det. Vi bekræftede, at vores server kører fint og returnerer gode svar, så lad os begynde at opbygge klienten.

2. Opsætning af redux kogepladeapp

For enkelheds skyld bruger vi en redux kedelplade, så vi kan få al opsætningen (f.eks. Babel, webpack, CSS osv.) Gratis. Jeg kan godt lide denne kedelplade, fordi dens opsætning er let at følge og kun er på klientsiden - hvilket gør den perfekt til denne tutorial.

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

Lad os navigere til // localhost: 3000 / for at se, om klientserveren kører.

Yay! Klienten kører. Det er tid for os at begynde at tilføje en GraphQL-klient. Igen er vores mål at nemt hente data fra serveren og gengive dem på destinationssiden (HomeView) uden megen indsats ved hjælp af GraphQL-forespørgsler.

3. Tilføjelse af GraphQL-klient (Apollo Client)

Installer pakkerne apollo-client, react-apollo og graphql-tag.

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

Åbn derefter filen src / containers / AppContainer.js, roden til vores Redux-app. Det er her, vi overfører redux-lageret til underordnede komponenter ved hjælp af udbyderen fra react-redux.

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

Vi er nødt til at initialisere en ApolloClient og erstatte udbyderen fra react-redux med ApolloProvider fra react-apollo.

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

Det er det! Vi har lige tilføjet en GraphQL-klient til en almindelig Redux-app.

Lad os gå videre og prøve vores første GraphQL-forespørgsel.

4. Fetching data with GraphQL queries

Open src/views/HomeView.js

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)

HomeView is a conventional Redux container (smart component). To use GraphQL queries instead of action dispatchers to fetch data, we will make some changes together.

  1. Remove mapDispatchToProps() and mapStateToProps() completely.
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. Add mapQueriesToProps() and define a GraphQL query that will fetch the author information. Notice how this is the exactly the same query that we tested in the beginning using the GraphIQL interface on the server.

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. Replace connect from react-redux with connect from react-apollo and pass mapQueriesToProps as argument. Once mapQueriesToProps is connected to ApolloClient, the query will automatically fetch the data from the backend when HomeView is rendered, and pass the data down through props.

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. Render the data that’s passed down from the props:

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)

    Med denne forespørgsel forventer vi at få alle forfattere med deres fornavne og indlæg. Gå videre og opdater browseren for at se, om vi får de rigtige data.

    Hvis alt gik godt, vil din HomeView-side se ud som ovenfor.

    6. Næste trin

    Denne tutorial udforsker kun en lille del af GraphQL og udelader mange koncepter, såsom opdatering af data på serveren eller brug af en anden backend-server (f.eks. Rails).

    Mens jeg arbejder på at introducere disse i efterfølgende tutorials, kan du læse Sashkos indlæg eller Apollo Client Doc for bedre at forstå, hvad der foregår under emhætten (for eksempel hvad skete der, da vi udskiftede udbyderen med ApolloProvider?).

    Gravning i kildekoden til GitHunt, en Apollo-klient og servereksempel-app i fuld stak, synes også at være en god måde at lære på.

    Hvis du har feedback, bedes du lade den være i kommentaren. Jeg vil gøre mit bedste for at være hjælpsom :)