상세 컨텐츠

본문 제목

[노마드코더] GraphQL + React.js + Apollo

개발 공부 (Nomadcoders)/GraphQL

by letprogramming 2021. 1. 21. 01:23

본문

반응형

[참고]https://nomadcoders.co

 

노마드 코더 Nomad Coders

코딩은 진짜를 만들어보는거야!. 실제 구현되어 있는 서비스를 한땀 한땀 따라 만들면서 코딩을 배우세요!

nomadcoders.co

Apollo - 서버와 통신할 때 클라이언트를 위한 패키지

1. 간단하고 쉬운 쿼리 => useQuery hooks를 이용해 쉽게 쿼리 전송 가능

2. Cache 존재 - 이미 불러온 데이터에 대해 자동으로 캐시 저장

 

이전에 사용했던 영화 API를 이용해 영화 리스트를 불러오고

영화를 클릭하면 해당 영화의 상세 정보 페이지로 들어가는 간단한 웹 페이지를 구현했다.

 

이전과 다른 점은 REST API가 아닌 GraphQL을 이용해 쿼리를 보내고 데이터를 받았다.

처음 경험해보기 때문에 익숙치 않았지만 한눈에 보아도 REST API보다 간단하고 쉽게 데이터를 주고 받았다.

JSON 파싱에 대한 걱정이나 메소드, URL에 관한 걱정이 많이 줄어들었다.

하나의 URI에 서버에서 정해놓은 쿼리만 보내면 원하는 정보만 원하는 타이밍에 받을 수 있었다.

 

또한 캐시를 자동으로 구현해주는 특징이 신기했다.

처음에 엔드 포인트에 도달하면 로딩이 되고 데이터를 받아 오지만,

이후에는 같은 URL에 대해서 FETCH받지 않고 캐시에 저장해 놓은 데이터를 이용해 훨씬 빠르게 서비스를 이용할 수 있었다.

 

Apollo가 아닌 다른 프레임워크의 경우 이러한 캐시를 구현하기 위해서 시간과 노력이 더 들어가야 하지만

자동으로 해결해주기 때문에 굳이 신경쓰지 않아도 되었다.

 

GraphQL에 대해서 많이 배웠고 이후에 백엔드에 대해 공부할 때 GraphQL과 Apollo를 제대로 사용해서 프로젝트에 적용하고 싶다.

마지막으로 다시 한 번 React Hooks의 편리함에 대해 깨닫게 되었다.

반응형

Apollo + GraphQL + React 사용법

1. 프로젝트를 만들 폴더로 이동

2. npx create-react-app [프로젝트명] => 자동으로 프로젝트 폴더 및 파일 생성

3. yarn add apollo-boost react-router-dom @apollo/react-hooks graphql (styled-components)

 

[index.js]

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import {ApolloProvider} from '@apollo/react-hooks';
import client from './apollo';

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

- ApolloProvider로 App 컴포넌트를 감싸주고 직접 만든 client를 명시해준다.

 

[apollo.js]

import ApolloClient from "apollo-boost"

const client = new ApolloClient({
    uri: "http://localhost:4000",
    resolvers: {
        Movie: {
            isLiked: () => false
        },
        Mutation: {
            toggleLikeMovie: (_, {id, isLiked}, {cache}) => {
                cache.writeData({
                    id: `Movie:${id}`,
                    data: {
                        isLiked: !isLiked
                    }
                });
            }
        }
    }
});

export default client;

- index.js의 ApolloProvider의 client가 정의되어 있는 곳

- ApolloClient를 생성할 때 uri만 넘겨주면 생성된다.

- resolvers 옵션을 통해 서버인 것처럼 클라이언트에서도 데이터를 보낼 수 있다.

위에서는 좋아요 버튼을 처리하기 위해 Movie와 Mutation을 새로 만들어 놓았다.

 

[components/App.js]

import React from "react";
import {HashRouter as Router, Route} from "react-router-dom";
import Home from "../routes/Home";
import Detail from "../routes/Detail";

function App() {
  return <Router>
    <Route exact path="/" component={Home}/>
    <Route exact path="/:id" component={Detail}/>
  </Router>
}

export default App;

- 기본 App 컴포넌트

- 사용할 라우터들을 정의해준다.

 

[components/Movie.js]

import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components"
import { useMutation, gql } from "@apollo/react-hooks";

const LIKE_MOVIE = gql`
    mutation toggleLikeMovie($id: Int!, $isLiked: Boolean!){
        toggleLikeMovie(id: $id, isLiked: $isLiked) @client
    }
`;

const Container = styled.div`
  height: 400px;
  border-radius: 7px;
  width: 100%;
  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
  background-color: transparent;
`;

const Poster = styled.div`
  background-image: url(${props => props.bg});
  height: 100%;
  width: 100%;
  background-size: cover;
  background-position: center center;
  border-radius: 7px;
`;

export default ({ id, bg, isLiked }) => {
    const [toggleMovie] = useMutation(LIKE_MOVIE, {
        variables: {id: parseInt(id), isLiked}
    });
    
    return (
        <Container>
            <Link to={`/${id}`}>
                <Poster bg={bg} />
            </Link>
            <button onClick={toggleMovie}>{isLiked ? "♥" : "♡"}</button>
        </Container>
    )
}

- Home에서 영화 리스트들을 보여줄 때 영화 하나에 대한 컴포넌트이다.

 

[routes/Home.js]

import React from "react";
import { gql } from "apollo-boost";
import { useQuery } from "@apollo/react-hooks";
import styled from "styled-components";
import Movie from "../components/Movie";

const GET_MOVIES = gql`
    {
        movies {
            id
            medium_cover_image
            isLiked @client
        }
    }
`;

const Container = styled.div`
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
`;

const Header = styled.header`
  background-image: linear-gradient(-45deg, #d754ab, #fd723a);
  height: 45vh;
  color: white;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
`;

const Title = styled.h1`
  font-size: 60px;
  font-weight: 600;
  margin-bottom: 20px;
`;

const Subtitle = styled.h3`
    font-size: 35px;
`;

const Loading = styled.div`
    font-size: 18px;
    opacity: 0.5;
    font-weight: 500;
    margin-top: 10px;
`;

const Movies = styled.div`
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-gap: 25px;
  width: 60%;
  position: relative;
  top: -50px;
`;

export default () => {
    const { loading, data } = useQuery(GET_MOVIES);
    console.log(data);
    return (
        <Container>
            <Header>
                <Title>GraphQL Apollo Movie App</Title>
                <Subtitle>I LOVE MOVIE</Subtitle>
            </Header>
            {loading && <Loading>Loading...</Loading>}
            <Movies>
                {data?.movies?.map(m => (
                    <Movie
                        key={m.id}
                        id={m.id}
                        isLiked={m.isLiked}
                        bg={m.medium_cover_image}
                     />
                ))}
            </Movies>
        </Container>
    );
}

- 들어갔을 때 가장 먼저 보이는 페이지로 영화 리스트를 쿼리를 통해 불러와서 보여준다.

 

[routes/Detail.js]

import React from "react";
import { useParams } from "react-router-dom";
import { gql } from "apollo-boost";
import { useQuery } from "@apollo/react-hooks";
import styled from "styled-components";

const GET_MOVIE = gql`
    query getMovie($id: Int!){
        movie(id: $id){
            title
            medium_cover_image
            language
            rating
            summary
            isLiked @client
        } #For Server
    } #For Apollo (WHEN NEED VARIABLE)
`;
const Container = styled.div`
  height: 100vh;
  background-image: linear-gradient(-45deg, #d754ab, #fd723a);
  width: 100%;
  display: flex;
  justify-content: space-around;
  align-items: center;
  color: white;
`;

const Column = styled.div`
  margin-left: 10px;
  width: 50%;
`;

const Title = styled.h1`
  font-size: 65px;
  margin-bottom: 15px;
`;

const Subtitle = styled.h4`
  font-size: 35px;
  margin-bottom: 10px;
`;

const Description = styled.p`
  font-size: 28px;
`;

const Poster = styled.div`
  width: 25%;
  height: 60%;
  background-color: transparent;
  background-image: url(${props => props.bg});
  background-size: cover;
  background-position: center center;
`;

export default () => {
    const { id } = useParams();
    const { loading, data } = useQuery(GET_MOVIE, {
        variables: { id: parseInt(id) }
    });

    return (
        <Container>
            <Column>
                <Title>
                    {loading ? "Loading..."
                    : `${data.movie.title} ${data.movie.isLiked ? "😍" : "😭"}`}
                </Title>
                <Subtitle>
                    {data?.movie?.language} - {data?.movie?.rating}
                </Subtitle>
                <Description>
                    {data?.movie?.summary}
                </Description>
            </Column>
            <Poster bg={data?.movie?.medium_cover_image}></Poster>
        </Container>
    );
};

- Home에서 영화를 클릭하고 들어가면 보이는 영화 상세 페이지

- 영화의 ID를 이용해 각각의 URL을 구성한다.

반응형

관련글 더보기