[React.js] GraphQL 서버와 통신하기

2021. 12. 21. 00:03ReactJS/Information

해당 글에는 GraphQL이 나오니 모른다면 이해가 어려울 수 있다.

아래 링크를 통해 먼저 알고 가도록 하자.

[ Graph QL ]

[ 완성 코드 보러가기 ]

 

[ 참고문서 : React-Query ]

→ 실제 사용은 되지 않으나 사용법이 매우 유사함.

 

라이브러리

React.js에서 GraphQL 서버와 통신할 때 주로 사용되는 라이브러리는 크게 3가지가 있다.

  • Relay
    퍼포먼스, 네트워크 트래픽 최적화 / 높은 러닝커브
  • Apollo Client
    데이터 캐싱, UI 업데이트
  • urql
    단순성과 확장성에 중점, React에 포커스 되어 있음, Relay, Apllo Client 보다 이후에 나왔음.

위 3개의 라이브러리에 모두 장단점이 있으니 찾아서 사용하면 될텐데, 이 글에서는 Apollo Client를 이용하였다.

 

Apollo Client

Apollo Client(이하 React Apollo)에서는 사용법이 2가지로 나뉜다.

Hooks 사용/미사용 으로 나뉘는데, 이 글에서는 Hook을 사용하는 것으로 채택하여 작성한다.

기본 설정

create-react-app으로 React.js App을 하나 생성해주고 아래 의존성을 설치해주도록 하자.

(이 글에서는 보다 좋은 설명을 위해 Typescript를 이용하였다.)

# npm
npm install apollo-boost @apollo/react-hooks graphql

# yarn
yarn add apollo-boost @apollo/react-hooks graphql

 

src/index.tsx를 아래와 같이 설정해주도록 하자.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {ApolloClient, ApolloProvider, InMemoryCache} from "@apollo/react-hooks";

const client = new ApolloClient({
    uri: 'http://localhost:8080',
    cache: new InMemoryCache()
})

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

(react-redux와 같이 사용할 경우 ApolloProvider를 store-provider 위에 감싸면 된다.)

 

여기서 client에 InMemoryCache는 캐싱처리에 대한 내용을 설정할 수 있다.

(참고 : https://www.apollographql.com/docs/react/caching/cache-configuration/)

 

uri는 localhost에 올라와있는 Spring boot GraphQL 서버를 이용했으며, 만약 Spring boot GraphQL 서버를 띄우지 못하는 환경이라면 GraphQL Fake Api 를 구글링하면 많이 나올테니, 그 서버를 이용하면 된다.

 

그 후 Query를 작성해주면 된다.

 

queries/Queries.ts

import {gql} from "graphql-tag";

export const __GET_POST__ = gql`
    query {
        getPost {
            title
        }
    }
`

이 때 getPost 쪽에서 빨간줄이 나올 수 있는데, 이는 Schema와 Type을 정의하지 않아서 그렇다.

이런 경우에 나는 IntelliJ IDEA를 사용하고 있어 자동적으로 지원을 해주고 있는데, 다른 개발 툴에서는 어떻게 적용하는지에 대해서는 찾아봐야 한다.

(검색어 : .graphqlconfig generate, schema.graphql auto generate)

 

IntelliJ의 경우 .graphqlconfig를 root 경로에 작성해주게 되면 원격 서버에 있는 Schema와 Type을 전부 자동으로 정의해준다.

 

.graphqlconfig

{
  "name": "y0ngha GraphQL Post & Comment Schema",
  "schemaPath": "./schema.graphql",
  "extensions": {
    "endpoints": {
      "Default GraphQL Endpoint": {
        "url": "http://localhost:8080/graphql",
        "headers": {
          "user-agent": "JS GraphQL"
        },
        "introspect": true
      }
    }
  }
}

위처럼 작성하고, Auto generation을 작동시킨다면, schemaPath에 정의되어있는 경로 & 파일명에 자동으로 작성된다.

(다시 한번 말하지만 IntelliJ IDEA 기준으로 다른 개발 툴에서는 어떻게 작동되는지에 대해서 찾아봐야 한다.)

 

작성 후 실행 버튼을 누르면,
원격 서버에 있는 내용을 전부 불러온다.

(이게 introspect 기능인가?)

 

여기까지 했으면 이제 빨간줄은 사라졌을거고, 테스트 컴포넌트를 만들어 한번 테스트해보자.

 

Post.tsx

import React from "react"
import { useQuery } from "@apollo/react-hooks";
import {__GET_POST__} from "../queries/Queries";

interface GetPostQuery {
    getPost: GetPost[]
}
interface GetPost {
    title: string
}

function Post() {
    const { loading, error, data } = useQuery<GetPostQuery>(__GET_POST__)
    console.log(error, data)
    return (
        <>
            {
                loading ? "Loading..." : ""
            }
            {
                error ? error.message : ""
            }
            {
                data?.getPost.length !== 0 &&
                    data?.getPost.map((it: GetPost) => {
                        return (
                            <>
                                {it.title}<br/>
                            </>
                        )
                    })
            }
        </>
    )
}

export default Post

(interface의 경우 다른 폴더를 생성해 한 폴더 내에서 관리를 해주는것이 좋으나, 테스트 목적으로 생성된 프로젝트기 때문에 컴포넌트 내부에 선언해줬다.)

 

apollo에서 지원하는 useQuery를 사용하면 되는데, 이것은 react-query의 사용법과 매우 유사하다.

(react-query 사용법은 스크롤을 맨 위로 올리면 링크가 있다.)

[ 공식 문서에서 Apollo Use Query에 대해서 살펴보기 ]

 

위와 같이 Post 컴포넌트를 렌더링 하게 되면, title이 보이게 될 것이다.

 

useQuery를 이용해 데이터를 읽었으니, 이제 useMutation을 통해 데이터를 패치 해보도록 하자.

데이터를 조작하기 위해 Post 컴포넌트를 간단하게 수정했다.

버튼을 누르게 되면 해당 글을 삭제하고, 리렌더링 하는 기능을 만들 것이다.

 

queries/Queries.ts

import {gql} from "graphql-tag";

export const __GET_POST__ = gql`
    query {
        getPost {
            id,
            title
        }
    }
`

 

Post.tsx

import React from "react"
import { useQuery } from "@apollo/react-hooks";
import {__GET_POST__} from "../queries/Queries";

interface GetPostQuery {
    getPost: GetPost[]
}
interface GetPost {
    id: number,
    title: string
}

function Post() {
    const { loading, error, data, refetch } = useQuery<GetPostQuery>(__GET_POST__)
    console.log(error, data)
    return (
        <>
            {
                loading ? "Loading..." : ""
            }
            {
                error ? error.message : ""
            }
            {
                data?.getPost.length !== 0 &&
                    data?.getPost.map((it: GetPost) => {
                        return (
                            <div className={"post"} key={it.id}>
                                <span key={`${it.id}-span`}>{it.title}</span><button key={`${it.id}-button`}>Delete</button><br/>
                            </div>
                        )
                    })
            }
        </>
    )
}

export default Post

저 button에다가 click Event를 부여해 업데이트를 진행할 것이다.

 

queries/Queries.ts

import {gql} from "graphql-tag";

export const __GET_POST__ = gql`
    query {
        getPost {
            id,
            title
        }
    }
`

export const __DELETE_POST__ = gql`
    mutation DeletePostById($id: Int!){
        deletePostById(id: $id)
    }
`

Post.tsx

import React, {useCallback} from "react"
import {useMutation, useQuery} from "@apollo/react-hooks";
import {__DELETE_POST__, __GET_POST__} from "../queries/Queries";

interface GetPostQuery {
    getPost: GetPost[]
}

interface GetPost {
    id: number,
    title: string
}

function Post() {
    const {loading, error, data, refetch} = useQuery<GetPostQuery>(__GET_POST__)
    const [dataDelete] = useMutation(__DELETE_POST__)
    console.log(error, data)

    const fetchDataDelete = useCallback(async (id: number) => {
        const result = await dataDelete({
            variables: {
                id: id
            }
        })
        refetch()
        console.log(result)
    }, [])
    return (
        <>
            {
                loading ? "Loading..." : ""
            }
            {
                error ? error.message : ""
            }
            {
                data?.getPost.length !== 0 &&
                data?.getPost.map((it: GetPost) => {
                    return (
                        <div className={"post"} key={it.id}>
                            <span key={`${it.id}-span`}>{it.title}</span>
                            <button key={`${it.id}-button`} onClick={async () => { await fetchDataDelete(it.id)}}>Delete</button>
                            <br/>
                        </div>
                    )
                })
            }
        </>
    )
}

export default Post

위와 같이 작성해주면, 버튼을 누를때마다 사라지는 것을 볼 수 있다.

 

만약, query도 위와 같이 조건을 주고 갖고오고 싶다면 아래와 같이 작성하면 된다.

export const __GET_POST_BY_ID_ = gql`
    query GetPostById($id: Int!) {
        getPostById(id: $id) {
            id,
            title
        }
    }
`

refetch를 이용해 다시 호출할 때 변수를 설정해줄 수 있다.

 

마치며

React.js에서 GraphQL을 적용하는 것은 생각보다 힘들었다.

아직 모르는것도 많고, 설정에 대해 미숙해 그러는 것 같다.

사실 이번 글은 단순히 사용법만 작성한 것이다.

더 공부해서 조금 더 심화적인 부분을 알아가야 잘 사용할 수 있을 것 같다.

우선 하나는 확실한게, 공통 Type을 정의해 CUD에서 성공했는지 실패했는지를 관리했었어야 했을 것 같다.

 

참고문서)

https://www.apollographql.com/docs/react/data/error-handling/

https://www.daleseo.com/graphql-apollo-remote-schemas/

https://d2.naver.com/helloworld/4245995

https://velog.io/@bangina/Apollo-client-GraphQL-React.js-%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83