Kako koristiti GraphQL u svojoj aplikaciji Redux

Dohvaćanje i upravljanje podacima u Reduxu zahtijeva previše posla. Kako ističe Saško Stubailo:

Nažalost, obrasci za asinkrono učitavanje podataka poslužitelja u aplikaciji Redux nisu toliko dobro uspostavljeni i često uključuju upotrebu vanjskih pomoćnih knjižnica, poput redux-saga. Morate napisati prilagođeni kôd da biste pozvali krajnje točke poslužitelja, protumačili podatke, normalizirali ih i umetnuli u spremište - a sve to prateći razne greške i stanja učitavanja.

Na kraju ovog vodiča naučit ćete kako riješiti ovaj problem dopuštajući Apollo Clientu da dohvaća podatke i upravlja njima. Više nećete morati pisati dispečere, reduktore i normalizatore višestrukih akcija za dohvaćanje i sinkronizaciju podataka između vašeg prednjeg i stražnjeg dijela.

Ali prije nego započnete s vodičem, pobrinite se da:

  • Znate osnove GraphQL upita - ako ste potpuno novi u GraphQL-u, trebali biste se vratiti nakon što obavite ovaj vodič.
  • Imate iskustva u radu s React-om / Redux-om - ako ne, trebali biste se vratiti nakon što napravite tutorial za reakcije i redux.

U ovom uputstvu proći ćemo zajedno kroz 6 odjeljaka.

  1. Postavljanje poslužiteljskog okruženja (brzo)
  2. Postavljanje aplikacije redux kotla
  3. Dodavanje GraphQL klijenta (Apollo klijent)
  4. Dohvaćanje podataka pomoću GraphQL upita
  5. Dohvaćanje još više podataka
  6. Sljedeći koraci

1. Postavljanje poslužiteljskog okruženja

Prvo, trebamo GraphQL poslužitelj. Najlakši način za pokretanje poslužitelja je dovršavanje ovog sjajnog vodiča.

Ako se osjećate lijeno, možete samo klonirati moj repo, što je gotovo isti poslužitelj koji biste dobili da sami vodite tutorial. Poslužitelj podržava GraphQL upite za dohvaćanje podataka iz SQLite DB-a.

Pokrenimo ga i provjerimo radi li ispravno:

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

Poslužitelj bi trebao biti pokrenut na // localhost: 8080 / graphql. Idite na tu stranicu i provjerite imate li radno GraphiQL sučelje s rezultatima poput ovog:

GraphiQL vam omogućuje da testirate različite upite i odmah vidite kakav odgovor dobivate od poslužitelja. Ako u odgovoru ne želimo prezime i poruku kolačića sudbine, možemo ažurirati upit kao u nastavku:

I upravo tako volimo. Potvrdili smo da naš poslužitelj radi u redu i daje dobre odgovore, pa krenimo s izradom klijenta.

2. Postavljanje aplikacije redux kotla

Radi jednostavnosti, poslužit ćemo se redux uzorkom kako bismo mogli besplatno dobiti sve postavke (npr. Babel, webpack, CSS itd.). Sviđa mi se ovaj obrazac jer je njegovo postavljanje lako pratiti i samo je na strani klijenta - što ga čini savršenim za ovaj vodič.

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

Idemo do // localhost: 3000 / da vidimo radi li klijentski poslužitelj.

Yay! Klijent je pokrenut. Vrijeme je da počnemo dodavati GraphQL klijenta. Opet, naš je cilj jednostavno dohvatiti podatke s poslužitelja i prikazati ih na odredišnoj stranici (HomeView) bez puno napora pomoću GraphQL upita.

3. Dodavanje GraphQL klijenta (Apollo klijent)

Instalirajte pakete apollo-client, response-apollo i graphql-tag.

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

Zatim otvorite datoteku src / container / AppContainer.js, korijen naše Redux aplikacije. Tu prenosimo redux trgovinu na podređene komponente, koristeći dobavljača iz response-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

Moramo inicijalizirati ApolloClient i zamijeniti dobavljača iz response-redux s ApolloProvider iz response-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

To je to! Upravo smo tako jednostavno dodali klijent GraphQL u običnu Redux aplikaciju.

Krenimo i isprobajmo naš prvi GraphQL upit.

4. Dohvaćanje podataka pomoću GraphQL upita

Otvorite 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 je konvencionalni Redux spremnik (pametna komponenta). Da bismo koristili GraphQL upite umjesto dispečera akcija za dohvaćanje podataka, zajedno ćemo napraviti neke promjene.

  1. U potpunosti uklonite mapDispatchToProps () i mapStateToProps ().
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. Dodajte mapQueriesToProps () i definirajte GraphQL upit koji će dohvatiti podatke o autoru. Primijetite kako je ovo potpuno isti upit koji smo testirali na početku koristeći GraphIQL sučelje na poslužitelju.

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. Zamijenite vezu iz response-redux s connect iz response-apollo i proslijedite mapQueriesToProps kao argument. Jednom kada se mapQueriesToProps poveže s ApolloClient, upit će automatski dohvatiti podatke iz pozadine kada se prikaže HomeView i prosljeđivati ​​podatke kroz rekvizite.

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. Renderirajte podatke koji su proslijeđeni iz rekvizita:

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 :)