Halo World

[GraphQL] GraphQL 클라이언트 만들어보기 본문

개발 지식/DEVELOPMENT

[GraphQL] GraphQL 클라이언트 만들어보기

_Yeony 2022. 2. 13. 22:38

 

본 포스팅은 Inflearn 얄코님 강의를 수강하며 복습용으로 작성하였습니다.

https://www.inflearn.com/course/%EC%96%84%ED%8C%8D%ED%95%9C-graphql-apollo/dashboard

 

[무료] 얄팍한 GraphQL과 Apollo - 인프런 | 강의

⚡ 짧고 굵은 전체 90분 강좌! 사이트의 코드들을 복붙하며 빠르게 GraphQL을 배우고 아폴로 사용법을 익히세요., - 강의 소개 | 인프런...

www.inflearn.com

 

React와 Apollo Client


실습을 위한

- 백앤드 서버 : https://gitlab.com/yalco/yalco-inflearn-graphql-apollo/-/tree/master/1-3-graphql-exp

- 클라이언트 서버 : https://gitlab.com/yalco/yalco-inflearn-graphql-apollo/-/tree/master/4-1-react-before-apollo

 

 

1. 아폴로 클라이언트 적용

아폴로 클라이언트 모듈 설치

npm install @apollo/client graphql

설치한 모듈 import

//app.js

// ...
import { ApolloProvider } from '@apollo/client';
import { ApolloClient, InMemoryCache } from '@apollo/client'
// ...


// ...
  const client = new ApolloClient({
    uri: 'http://localhost:4000',
    cache: new InMemoryCache()
  });
  // ...
  • client : GraphQL 서버와 정보를 주고받을 ApolloClient 객체 (axios 같은 역할)
  • url : GraphQL 서버 주소
  • cache : InMemoryCache를 통한 캐시 관리
//app.js

//   ...
  return (
    <div className="App">
     //랜더링 하는 부분의 안쪽을 ApolloClient로 감싸줌
      <ApolloProvider client={client}>
        <header className="App-header">
          <h1>Company Management</h1>
          <nav>
            <ul>
              {NavMenus()}
            </ul>
          </nav>
        </header>
        <main>
          {mainComp[menu]}
        </main>
      </ApolloProvider>
    </div>
  );
//   ...

 

2. GraphQL 서버로부터 목록 받아와 표시하기

//roles.js

// ...
import { useState } from 'react';
import { useQuery, gql } from '@apollo/client';
// ...

// ...
const GET_ROLES = gql`  //아래 내용을 바탕으로 graphQL에 보낼 요청 객체를 만듦
  query GetRoles {
    roles {
      id
    }
  }
`;
// ...
  • 랜더링될 컨텐츠 id를 저장한 state 지정
//role.js

function Roles() {
    // ...
      const [contentId, setContentId] = useState('');
    // ...

}
  • 서버로부터 데이터를 받아와서 띄워주기 위한 메소드 작성
// ...
  function AsideItems () {
    const roleIcons = {
      developer: '💻',
      designer: '🎨',
      planner: '📝'
    }
    const { loading, error, data } = useQuery(GET_ROLES);	//위에서 선언한 GraphQL 쿼리 이용
    
    //로딩과 에러 설정
    if (loading) return <p className="loading">Loading...</p>
    if (error) return <p className="error">Error :(</p>
    
    return (
      <ul>
        {data.roles.map(({id}) => {
          return (
            <li key={id} className={'roleItem ' +  (contentId === 'id' ? 'on' : '')}
            onClick={() => {setContentId(id)}}>
              <span>{contentId === id ? '🔲' : '⬛'}</span>
              {roleIcons[id]} {id}
            </li>
          )
        })}
      </ul>
    );
  }
// ...

 

3. GraphQL 서버로부터 id로 컨텐츠 받아와 표시하기

//roles.js

// ...
const GET_ROLE = gql`
  query GetRole($id: ID!) {	//특정 Id에 대한 role정보 가져오는 graphQL 쿼리문
    role(id: $id) {
      id
      requirement
      members {
        id
        last_name
        serve_years
      }
      equipments {
        id
      }
      softwares {
        id
      }
    }
  }
`;
// ...
//role.js

  function MainContents () {

//위에서 설정한 id로 특정 role 정보를 가져오는 쿼리 사용
    // id는 contentId state를 전달
    const { loading, error, data } = useQuery(GET_ROLE, {
      variables: {id: contentId}
    })

    if (loading) return <p className="loading">Loading...</p>
    if (error) return <p className="error">Error :(</p>
    if (contentId === '') return (<div className="roleWrapper">Select Role</div>)

    return (
      <div className="roleWrapper">
        <h2>{data.role.id}</h2>
        <div className="requirement"><span>{data.role.requirement}</span> required</div>
        <h3>Members</h3>
        //가져온 정보 렌더링 해주기
        <ul>
          {data.role.members.map((member) => {
            return (<li>{member.last_name}</li>)
          })}
        </ul>
        <h3>Equipments</h3>
        <ul>
          {data.role.equipments.map((equipment) => {
            return (<li>{equipment.id}</li>)
          })}
        </ul>
        <h3>Softwares</h3>
          {data.role.softwares.map((software) => {
            return (<li>{software.id}</li>)
          })}
        <ul>
        </ul>
      </div>
    );
  }

 

Query와 Mutation을 사용하여 웹페이지 만들기


모듈 로드

//teams.js

// ...
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client'
// ...

상태를 저장할 state 준비

//teams.js

// ...
  const [contentId, setContentId] = useState(0)
  const [inputs, setInputs] = useState({
    manager: '',
    office: '',
    extension_number: '',
    mascot: '',
    cleaning_duty: '',
    project: ''
  })
// ...

 

1. 팀 목록 받아오기

  • 쿼리 작성
//teams.js

// ...
const GET_TEAMS = gql`
  query GetTeams {
    teams {	//team 스키마에 저장된 id, manager
        id
        manager
        members {	//member 스키마에 저장된 특정 값을 받아옴
          id
          first_name
          last_name
          role
        }
      }
  }
`;
// ...
  • 아래처럼 useQeury를 이용해 해당 쿼리를 실행시킬 수 있다.
const { loading, error, data, refetch } = useQuery(GET_TEAMS);
  • 실행되면 data에 받아온 데이터가 들어오게 되며, 데이터에 접근해 렌더링 시켜줄 수 있다.
return (
      <ul>
        {data.teams.map(({id, manager, members}) => {
          return (
            <li key={id}>
              <span className="teamItemTitle" onClick={() => {setContentId(id)}}>
                Team {id} : {manager}'s
              </span>
              <ul className="teamMembers">
                {members.map(({id, first_name, last_name, role}) => {
                  return (
                    <li key={id}>
                      {roleIcons[role]} {first_name} {last_name}
                    </li>
                  )
                })}
              </ul>
            </li>
          )
        })}
      </ul>
    )

 

2. Mutation 사용

  • 항목 삭제하기
// ...
const DELETE_TEAM = gql`
  mutation DeleteTeam($id: ID!) {
    deleteTeam(id: $id) {
      id
    }
  }
`
// ...
  function execDeleteTeam () {
    if (window.confirm('이 항목을 삭제하시겠습니까?')) {
      deleteTeam({variables: {id: contentId}})
    }
  }
  
  //작성한 delete 쿼리를 이용해 useMutation으로 쿼리 날림
  //쿼리가 실행된 후에는 deleteTeamCompleted 함수를 실행하도록 함
  const [deleteTeam] = useMutation(
  DELETE_TEAM, { onCompleted: deleteTeamCompleted })
  
  function deleteTeamCompleted (data) {
    console.log(data.deleteTeam)
    alert(`${data.deleteTeam.id} 항목이 삭제되었습니다.`)
    setContentId(0)
  }
    // ...
  • delete 버튼에 execDeleteTeam를 적용하여 실행
// ...
    <button onClick={execDeleteTeam}>Delete</button>
// ...

 

3. Fetch 사용

  • 항목을 삭제/삽입/수정한 후 리스트를 갱신하기위해서 refetch를 이용할 수 잇다.
// ...
//함수 밖에서 refetch를 넣을 refetchTeams 변수 선언
let refetchTeams


// ...
//useQuery의 매개변수 중 refetch를 위에서 선언한 refetchTeams 변수에 넣어준다.
const { loading, error, data, refetch } = useQuery(GET_TEAMS);
refetchTeams = refetch
// ...
//항목 삭제이후 부분에서 refetchTeams()를 호출해줌으로써 refetch 실행할 수 있다.
alert(`${data.deleteTeam.id} 항목이 삭제되었습니다.`)
refetchTeams()
// ...

 

Fragment 사용하기


Fragment

  • 여러 쿼리에 사용될 수 있는, 재사용 가능한 필드셋 (반복되는 쿼리를 재사용하여 중복을 줄임)
  • 중복을 줄임으로써 전체 코드를 간소화
  • 서버단에서는 유니언, 인터베이스로 쿼리 중복을 간소화했다면 클라이언트에서는 fragment로 간소화
  • https://graphql-kr.github.io/learn/queries/

 

예를 들어, 아래와 같은 쿼리는

const GET_PEOPLE = gql`
  query GetPeople {
  people {
    id
    first_name
    last_name
    sex
    blood_type
    }
  }
`;

const GET_PERSON = gql`
  query GetPerson($id: ID!) {
    person(id: $id) {
      id
      first_name
      last_name
      sex
      blood_type
      serve_years
      role
      team
      from
      tools {
        __typename
        ... on Software {
          id
        }
        ... on Equipment {
          id
          count
        }
      }
    }
  }
`;

 

아래처럼 재사용되는 요소들을 fragment로 분리하여 중복을 제거할 수 있다.

const Names = gql`
  fragment names on People {
    first_name
    last_name
  }
`
const HealthInfo = gql`
  fragment healthInfo on People {
    sex
    blood_type
  }
`
const WorkInfo = gql`
  fragment workInfo on People {
    serve_years
    role
    team
    from
  }
`
const GET_PEOPLE = gql`
  query GetPeople {
  people {
    id
    ...names
    ...healthInfo
    }
  }
  ${Names}
  ${HealthInfo}
`;
  • fragment는 선언시 'fragment'로 선언하면 된다.