Apollo Client - ParkEunwoo/seoul-smart-app GitHub Wiki

Apollo Client

Apollo Client๋ž€?

apollo client๋Š” javascript app์˜ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. ๊ธฐ์กด redux๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„ํŽธํ•˜๊ณ  ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์‰ฝ๊ณ  ๊ฐ„ํŽธํ•˜๋‹ค. redux์—์„œ๋Š” ๊ฐ€์ ธ์˜ฌ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ์„œ๋ฒ„์˜ url์ด ํ•„์š”์ง€๋งŒ ์„œ๋ฒ„๊ฐ€ graphql๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค๋ฉด ํ•œ๋ฒˆ์˜ url์ž‘์„ฑ์œผ๋กœ graphql์— ์‰ฝ๊ฒŒ query๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

Apollo Boost๋กœ ์‰ฝ๊ฒŒ ์‹œ์ž‘ํ•˜๊ธฐ

apollo client๋Š” cache, http๋“ฑ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ค์ •ํ•ด์•ผํ•˜๋Š” ๊ฒƒ๋“ค์ด ์กด์žฌํ•œ๋‹ค. ํ•˜์ง€๋งŒ apollo boost๋ฅผ ์ด์šฉํ•ด ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

yarn add apollo-boost

apolloClient.js ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ ๊ธฐ๋ณธ ์„ค์ •์„ ํ•ด์ค€๋‹ค.

import ApolloClient from 'apollo-boost';

export const client = new ApolloClient({
  uri: 'http://seoul-smart-api.herokuapp.com',
});

App.js ํŒŒ์ผ์—์„œ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ provider๋กœ ๊ฐ์‹ธ์ค€๋‹ค. provider๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด apollo/react-hook์„ ์‚ฌ์šฉํ•œ๋‹ค.

yarn add @apollo/react-hooks
...
import { ApolloProvider } from '@apollo/react-hooks';
import client from './apolloClient.js';

export default function App(props) {
    ...
    return (
        <ApolloProvider client={client} >
            <View />
        </ApolloProvider>
    );
}

์ด๋Ÿฌ๋ฉด App์ด ํฌํ•จํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค์—์„œ apollo๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผํ• ๊นŒ?

graphql query ์ž‘์„ฑํ•˜๊ธฐ

yarn add graphql-tag

graphql์— query๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ชจ๋“ˆ์ด๋‹ค. query๋“ค์„ ํ•œ ๊ณณ์— ๋ชจ์•„๋‘๊ธฐ ์œ„ํ•ด์„œ query ํด๋”๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ํด๋”์•ˆ์— index.js ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค. ์˜ˆ์‹œ๋กœ ์žฅ์†Œ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ฒ ๋‹ค.

import gql from 'graphql-tag';

const GET_PLACES = gql`
query getPlaces($page:Int) {
  getPlaces(page: $page) {
    id
    name
  }
}
`;

export {
    GET_PLACES
};

์œ„์™€๊ฐ™์ด gql๋’ค์— ~์•„๋ž˜์— ์žˆ๋Š” ` ์•ˆ์— query๋ฌธ์„ ์ž‘์„ฑํ•ด์ค€๋‹ค. ๋งŒ์•ฝ ์ฟผ๋ฆฌ๋ฌธ์— input๊ฐ’์ด ๋“ค์–ด๊ฐ€๋Š” ๊ฒฝ์šฐ ์œ„์— ํ•ด๋‹น query๋ฌธ์„ ๋‹ค์‹œ ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด์ฃผ๊ณ  input๋ฐ›์„ ๊ฐ’์˜ ์Šคํ‚ค๋งˆ๋ฅผ ์ •ํ•ด์ค€๋‹ค. ์ด๋•Œ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ๋  ์ด๋ฆ„ ์•ž์— $๋ฅผ ๋ถ™์—ฌ์ค€๋‹ค.

mutation๋„ ์ด์–ด์„œ ์ž‘์„ฑํ•ด๋ณด์ž.

import gql from 'graphql-tag';

...
const SIGN_UP = gql`
mutation createUser($token: String!, $name: String!) {
    createUser(token: $token, name: $name) {
        id
        name
        achievement
        activityLog
    }
}
`
export {
    GET_PLACES,
    SIGN_UP
}

๋ฐ์ดํ„ฐ ํ™”๋ฉด์— ๋„์šฐ๊ธฐ

์ž‘์„ฑํ•œ query๋ฌธ์„ ์‹ค์ œ๋กœ component์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ react hook์„ ์ด์šฉํ•œ๋‹ค. ๊ผญ ๊ทธ๋ž˜์•ผํ•˜๋Š” ๊ฑด ์•„๋‹ˆ์ง€๋งŒ ์šฐ๋ฆฌ๋Š” hook์„ ์ด์šฉํ•œ๋‹ค ์ด์ œ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ์šฉ๋  ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž‘์—…์„ ์‹œ์ž‘ํ•œ๋‹ค. ์žฅ์†Œ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ค˜์•ผํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๊ณ  ๋ชฉ๋ก์—์„œ ๋„์šธ ์•„์ดํ…œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.

...
import { useQuery } from '@apollo/react-hooks';
import PlaceItem from './PlaceItem';
import { GET_PLACES } from './../query';

export default function PlaceList(){
    const { loading, error, data } = useQuery(
        GET_PLACES,
        { variables: { page: 1 } }
    );
    
    if(loading) return <View>Loading...</View>;
    if(error) return <View>Error...</View>;
    
    const placeList = data.getPlaces.map(({id, name}) => <PlaceItem key={id} name={name} />)
    return (
        <View>
            {placeList}
        </View>
    );
}

useQuery๋ฅผ ์ด์šฉํ•ด loading, error, data๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. data์— ํ•ด๋‹น resolver์ด๋ฆ„์œผ๋กœ ์ €์žฅ๋œ ์ •๋ณด๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š”๋ฐ ์žฅ์†Œ๋Š” ๋ฐฐ์—ด์ด๊ธฐ ๋•Œ๋ฌธ์— mapํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ๊ฐ๊ฐ์„ ๋ฆฌ์ŠคํŠธ์— ํ‘œ์‹œํ–ˆ๋‹ค.

mutation๋„ hook์„ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. useMutation์€ useQuery์™€ ๋‹ค๋ฅด๊ฒŒ mutation query๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

...
import { useState } from 'react';
import { useMutation } from '@apollo/react-hooks';
import { SIGN_UP } from './../query';

export default function SignUp(){
    const [signUp, { loading, error, data }] = useMutation(SIGN_UP);
    const [token, setToken] = useState();
    const [name, setName] = useState();
    
    return (
        <View>
            <TextInput value={token} onChangeText={(text) => setToken(text)}></TextInput>
            <TextInput value={name} onChangeText={(text) => setName(text)}></TextInput>
            <TouchableOpacity onClick={() => singUp({ variables: { token, name } })} >
                <Text>๋ฒ„ํŠผ</Text>
            </TouchableOpacity>
        </View>
    );
}

react hook์„ ์ด์šฉํ•ด state๋ฅผ ์ƒ์„ฑํ•˜๊ณ  input์ฐฝ์—์„œ ์ž…๋ ฅ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ state์— ๋„ฃ๊ณ  useMutaion์œผ๋กœ ๋งŒ๋“  signUp ํ•จ์ˆ˜์— ๋„ฃ์œผ๋ฉด ํ•ด๋‹น ๊ฒฐ๊ณผ๊ฐ€ loading, error, data์— ๋“ค์–ด์˜ค๊ฒŒ ๋œ๋‹ค.

Apollo Cache

apollo์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์€ redux๋ฅผ ๋Œ€์ฒดํ•ด client์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. apollo๋Š” cache ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ํ•œ๋ฒˆ ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋„ ์ €์žฅํ•ด๋‘๊ณ  ๋‹ค์‹œ ์š”์ฒญ์„ ํ•  ๋•Œ๋Š” cache๋ฅผ ํ•œ๋ฒˆ ํ™•์ธํ•œ ํ›„์— ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์š”์ฒญ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. ๋กœ์ปฌ์—์„œ๋„ cache ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ฒ ๋‹ค.

apolloClient.js์™€ ๊ฐ™์€ ๊ณณ์— clientState.js ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค. ์ด ํŒŒ์ผ์—์„œ๋Š” default, schema, resolver๋ฅผ ์ž‘์„ฑํ•ด ์ค„ ๊ฒƒ์ด๋‹ค.

apolloClient.js

import ApolloClient from "apollo-boost";
import clientState from "./clientState";

const client = new ApolloClient({
  uri: "http://seoul-smart-api.herokuapp.com",
  clientState
});

export default client;

graphql ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๋•Œ์ฒ˜๋Ÿผ ๋™์ผํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋ฉด ๋œ๋‹ค. ์•ฑ ๋‚ด์—์„œ ํŽธ์ง‘ํ•˜๋˜ ํ™œ๋™๋“ค์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

clientState.js

import { EDIT_FRAGMENT } from "./fragments";

const defaults = {
    edits: [{
        __typename: 'Edit',
        editing: false,
        id: 'new',
        name: 'ํ™œ๋™์ด๋ฆ„',
        total: 3,
        date: '2019-09-22',
        startTime: '02:00',
        endTime: '03:00',
        placeId: '์žฅ์†Œid',
        room: '์ง€๋Œ€',
        content: '๋‚ด์šฉ',
        type: 'mentoring'
    }]
};
const typeDefs = [`
      schema {
          query: Query,
          mutation: Mutation,
      }
      type Query {
          edits: [Edit]!
          edits(id: String!): Edit!
      }
      type Mutation {
          modifyEdit(id: String!): Edit!
      }
      type Edit {
          editing: Boolean!
          id: String
          name: String
          total: Int
          date: String
          startTime: String
          endTime: String
          placeId: String
          room: String
          content: String
          type: String
      }
`];
const resolvers = {
  Query: {
    edits: (_, variables, { cache }) => {
      const id = cache.config.dataIdFromObject({ __typename: "Edit" });
      return cache.readFragment({ frament: EDIT_FRAGMENT, id });
    }
  },
  Mutation: {
      modifyEdit: (_), variables, { cache }) => {
          const editQuery = cache.readQuery({query: GET_EDITS});
          const {name, total, date} = variables;
          const modifiedEdit = {
              __typename: 'Edit',
              name, total, date,
              ...
          }
          cache.writeData({
              data: {
                  edits: [modifedEdit, ...edits]
              }
          });
          return modifedEdit;
      }
      
  }
};

export default {
  defaults,
  typeDefs,
  resolvers
};

resolver๋ถ€๋ถ„๋งŒ ์ฐจ์ด๊ฐ€ ๋งŽ์ด ๋‚˜๋Š”๋ฐ ๊ธฐ์กด ์„œ๋ฒ„๋Š” mongodb์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๋‹ค๋ฉด ์—ฌ๊ธด cache์—์„œ ๊ฐ€์ ธ์™€์•ผํ•œ๋‹ค. ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์€ cache.dataIdFromObject๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์•ˆ์— query๋ฌธ์„ ๋„ฃ์–ด์•ผ ํ•˜๋Š”๋ฐ ์ž์ฃผ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— fragment๋กœ ๋งŒ๋“ค์–ด์„œ ์ €์žฅ์„ ํ•ด๋‘”๋‹ค.

fragment.js

import gql from "graphql-tag";

export default EDIT_FRAGMENT = gql`
  fragment EditPars on Edit {
      editing
      id
      name
      total
      date
      startTime
      endTime
      placeId
      room
      content
      type
  }
`;

์ด๋ ‡๊ฒŒ clientState๊ฐ€ ์™„์„ฑ๋˜์—ˆ๋‹ค. ์ด์ œ ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ํด๋”์— queries.jsํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค. queries.js

import gql from "graphql-tag";

export const GET_EDIT = gql`
  {
    edit @client {
      editing
      title
      content
    }
  }
`;

์ฟผ๋ฆฌ๋ฌธ ์˜†์— @client๋ฅผ ๋ถ™์—ฌ์ฃผ๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ  ๊ธฐ์กด๊ณผ ๊ฐ™๋‹ค.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ