Apollo 1일차

박병준·2021년 7월 19일
0

Apollo 뿌수기

목록 보기
1/1
post-thumbnail

#0.0 SetUp

  1. 프로젝트를 만들고 싶은 디렉토리로 가서 react app을 만들어준다.
    npx create-react-app 프로젝트명
  2. 프로젝트의 src폴더에서 App.js와 index.js를 제외한 파일들을 삭제해준다.
  3. src폴더 아래 components폴더를 만들고 App.js를 넣어준다.
  4. 필요한 패키지들을 설치해준다.
    yarn add styled-components react-router-dom apollo-boost @apollo/react-hooks graphql
  5. 깃허브에 repository를 만들고 연결시킨다.
  6. src폴더에 route폴더를 만들고 Home.js와 Detail.js를 만든후 App.js에서 router를 만들어준다.
//App.js
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 path="/:id" component={Detail} />
    </Router>
  );
}

export default App;
  1. public폴더에 reset.css를 만들고 index.html에 연결해준다. 각 브라우저마다 설정되어 있는 기본 스타일이 다 다르기 때문에 초기화 해준다. 나는 Eric Meyer’s “Reset CSS” 2.0를 사용하였다.

#1.0 Apollo Client

기본적으로 query를 Axios의 fetch와 함께 POST request로 보내야한다. 하지만 이러한 방식으로 하면 반복적으로 POST를 해줘야한다. 이러한 것들을 Apollo에서 자동적으로 쉽게 해준다.

여기서 http://localhost:4000/은 저번 GraphQL 수업에서 만들어 놓은 프로젝트를 실행시켜 놓으면 된다.

  1. client를 만든다.
//apollo.js
import { ApolloClient } from "@apollo/client";

const client = new ApolloClient({
    uri: "http://localhost:4000/",
    cache: new InMemoryCache(),
});

export default client;
  1. index.js에서 React Application을 위 client로 감싼다.
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import { ApolloProvider } from '@apollo/client';
import client from "./apollo";

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

#1.1 GET MOVIES & MOVIE Query

영화의 id와 이미지를 가져오는 query를 받아온다.

Movies Query

//Home.js
import React from "react";
import { gql } from "apollo-boost";
import { useQuery } from "@apollo/client";

const GET_MOVIES = gql`
    {
  movies{
    id
    medium_cover_image
  }
}
`;

export default () => {
    const { loading, error, data } = useQuery(GET_MOVIES);
    if (loading) {
        return "loading...";
    } else {
        return data.movies.map(m => <h1>{m.id}</h1>);
    }
};

Movie Query

//Detail.js
import React from "react";
import { useParams } from "react-router-dom";
import { gql } from "apollo-boost";
import { useQuery } from "@apollo/client";

const GET_MOVIE = gql`
  query getMovie($id: Int!) { //이부분은 Apollo에서 변수의 type을 검사하도록 한다.
    movie(id: $id) { //여기서부터 아래부분만 query로 내 서버로 간다.
      id
      title
      medium_cover_image
      language
      rating
      description_intro
    }
    suggestions(id: $id) {
      id
      medium_cover_image
    }
  }
`;

export default () => {
    const { id } = useParams();// url의 파마미터를 받아올 수 있다.
    const { loading, data } = useQuery(GET_MOVIE, {
        variables: { id: parseInt(id) }
    });
    if (loading) {
        return "loading...";
    }
  	if (data && data.movie){
        return data.movie.title;
    }
};

#1.2 Apollo Cache

Apollo는 cache를 가지고 있디. Apollo는 뭔가를 얻으면 저장한다. 만약 다시 같은 id로 이동한다면 다시 request하지 않고 cahce에서 꺼내 보여준다. user가 loading창을 아주 덜 보게 되는 것이다.


#1.3 Data & Apollo Dev Tools

랜더링 하는 곳에 바로 data를 쓰게 되면 data를 받아오기 전이기 때문에 movie가 없다는 error가 뜬다. 그래서 항상 loading과 같이 써줘야한다.
{loading ? "Loading..." : data.movie.title}
{!loading && data.movie && (<><Description>{data.movie.description_intro}</Description></>)}

javascript에 optional chaining이 추가되어 아래와 같이 바로 쓸 수 있다.
<Description>{data?.movie?.description_intro}</Description>

Apollo Dev Tool을 이용하면 브라우저에서 Apollo에서 관리하는 cache와 query들을 볼 수 있다.


#2.0 Local State

Local state는 API에서 넘어온 data를 바꿀 수 있다.

  1. apollo.js에서 resolvers를 추가해준다.
//apollo.js
import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
    uri: "http://localhost:4000/",
    cache: new InMemoryCache(),
    resolvers: {
        Movie: {
            isLiked: () => false
        }
    }
});

export default client;
  1. apollo dev tool로 cache에 isLiked가 추가된것을 볼 수 있다.

Local state의 값을 바꾸고 싶다면 client에 mutation을 생성해서 바꿀 수 있다. backend에서의 resolver를 사용할 수 있다.

//apollo.js
import { ApolloClient, InMemoryCache } from "@apollo/client";

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

export default client;
//Movie.js
const LIKE_MOVIE = gql`
  mutation toggleLikeMovie($id: Int!, $isLiked: Boolean!) {
    toggleLikeMovie(id: $id, isLiked: $isLiked) @client 
  }
`;
//@client를 통해 apollo에 mutation이 client에 있다고 알려줘야한다.

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 ? "Unlike" : "Like"}</button>
        </Container>
    );
};

#2.1 connecting Detail and Home

Detail.js에서 Movie query를 받아올 때 id도 같이 받아와야 movie와 movies의 값이 동일하게 바뀐다.

//Detail.js
const GET_MOVIE = gql`
  query getMovie($id: Int!) {
    movie(id: $id) {
      id
      title
      medium_cover_image
      language
      rating
      description_intro
      isLiked @client
    }
    suggestions(id: $id) {
      id
      medium_cover_image
    }
  }
`;

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?.description_intro}</Description>
            </Column>
            <Poster bg={data?.movie?.medium_cover_image}></Poster>
        </Container>
    );
};

하지만 위의 방식으로 하면 Detail.js에서 movie query를 다시 부르기 때문에 isLiked가 flase로 초기화 된다. 영화를 한번은 클릭해야지만 like기능을 쓸 수 있었다.

profile
뿌셔뿌셔

0개의 댓글