티스토리 뷰

# 시작

이번 포스팅에서는 Apollo Client를 이용해 이전에 구축한 서버에서 샘플 데이터를 가져옵니다.

필요한 의존성은 이전 포스팅(https://jee-goo.tistory.com/entry/React-Parcel-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1-FrontEnd-1)에서 전부 설치했기 때문에 바로 시작합니다.


# Apollo Client 및 Route 구성

다음 파일들을 생성합니다.

src/ApolloClient.tsx

import ApolloClient from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';

const link = new HttpLink({
  uri: 'http://localhost:8000/graphql' // `SpringBoot & Graphql 포스팅에서 구축한 서버의 URI`/graphql
});

const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
});

export default client;

@ApolloClient

uri 속성에 `Spring Boot & Graphql` 포스팅에서 구성했던 서버의 uri와 EndPoint를 정의해주면 Query를 이용할 때 해당 uri로 데이터를 요청합니다.

이후 App.tsx에서 ApolloProvider의 client 속성에 이 컴포넌트를 넘겨주기만 하면 됩니다. 그럼 Query를 통해 데이터를 가져올 수 있습니다.


src/components/shared/Home.tsx

import React from 'react';

const Home = () => {
  return (
    <section id="intro" className="wrapper style1 fullscreen fade-up">
      <div className="inner">
        <h1>MyBlog</h1>
        <p>Welcome to MyBlog</p>
      </div>
    </section>
  )
}

export default Home;

src/components/shared/RouteComponent.tsx

import React from 'react'
import { Route } from 'react-router-dom';

import Home from './Home';
import Sample from '../sample/Sample';

const RouteComponent = () => {
  return (
    <div id="wrapper">
      <Route exact={true} path="/" component={Home} />
      <Route path="/Sample" component={Sample} />
    </div>
  )
}

export default RouteComponent;

@exact

path에 설정된 경로와 정확하게 똑같은 요청에만 정의된 컴포넌트를 보여줍니다.


src/components/shared/Sidebar.tsx

import React from 'react';
import { Link } from 'react-router-dom';

const Sidebar = () => {
  return (
    <section id="sidebar">
      <div className="inner">
        <nav>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/Sample">Sample</Link></li>
            <li><Link to="/Sample2">Sample2</Link></li>
            <li><Link to="/Sample3">Sample3</Link></li>
          </ul>
        </nav>
      </div>
    </section>
  )
}

export default Sidebar;

@Link

<a> 태그 대신 사용하는 컴포넌트입니다.

<a> 태그의 href속성을 사용하게 되면 새로고침을 하게 됩니다.

새로고침을 하지 않고 라우팅을 하기 위해 사용합니다.


src/App.tsx를 다음과 같이 수정합니다.

import React, { Fragment } from 'react';
import { ApolloProvider as ApolloHooksProvider } from 'react-apollo-hooks';
import { BrowserRouter as Router } from 'react-router-dom';
import client from './ApolloClient';
import Sidebar from './components/shared/Sidebar';
import RouteComponent from './components/shared/RouteComponent';

const App = () => {
  return (
    <ApolloHooksProvider client={client}>
      <Fragment>
        <Router>
            <Sidebar />
            <RouteComponent />
        </Router>
      </Fragment>
    </ApolloHooksProvider>
  )
}

export default App;

@ApolloHooksProvider

react-apollo에서 useQuery라는 hook을 사용하기 위해 필요합니다. 

client 속성에 위에서 만들었던 ApolloClient 컴포넌트를 넘겨줍니다. 


# Sample Page 생성

Apollo 구성이 완료되었으니 이제 샘플 페이지를 생성합니다.

먼저 샘플 목록의 한 행을 나타낼 컴포넌트를 작성합니다.

src/components/sample/City.tsx

import React, { FunctionComponent } from 'react';

interface City {
  id: number
  name: string
  population: number
}

const City: FunctionComponent<City> = (props) => {
  return (
    <tr>
      <td>{props.id}</td>
      <td>{props.name}</td>
      <td>{props.population}</td>
    </tr>
  )
}

export default City;

@interface

FunctionComponent에 props의 타입을 명시합니다. 즉, City가 props의 타입이 되겠죠.

또한 interface City에 각 속성들의 타입을 명시하고 있습니다.

여기서 주의할 점은 tsconfig.json파일의 compilerOptions에서 strictNullChecks속성이 false라면 모든 타입은 null, undefined를 허용합니다.

반대로 true라면 null, undefined타입까지 고려하여 개발해야겠죠. 이런 경우에는 아래와 같이 타입을 정의합니다.

가능하다면 아래와 같이 개발하는 것이 작성할 때는 불편해도 결과적으로 좋습니다.

interface City {
  id: number
  name: string | undefined
}

이번엔 샘플 목록 컴포넌트를 생성합니다.

src/components/sample/CityList.tsx

import React, { FunctionComponent } from 'react';
import City from './City';

interface ICity {
  id: number
  name: string
  population: number
}

interface ICityList {
  cityList: [ICity]
}

const CityList: FunctionComponent<ICityList> = ({ cityList }) => {

  const makeCityList = () => {
    // (1)
    return cityList.map((city: ICity) => (
      <City
        key={city.id}
        id={city.id}
        name={city.name}
        population={city.population}
      />
    ));
  }

  return (
    <section className="wrapper style2 fullscreen">
      <h2>City List</h2>
      <div className="table-wrapper">
        <table>
          <thead>
            <tr>
              <th>Id</th>
              <th>Name</th>
              <th>Population</th>
            </tr>
          </thead>
          <tbody>
            // (2)
            {makeCityList()}
          </tbody>
        </table>
      </div>
    </section>
  )
}

export default CityList;

@(1)

map은 javascript Array의 내장 함수입니다.

배열의 첫 번째 요소부터 마지막 요소까지 반복하며 콜백 함수를 실행하여 반환된 값들을 모아 새로운 배열로 반환합니다.

@(2)

(1)에서 정의한 함수를 실행하여 City 컴포넌트의 배열을 삽입합니다.


마지막으로 샘플 페이지를 만듭니다.

src/components/sample/Sample.tsx

import React from 'react';
import gql from 'graphql-tag';
import { useQuery } from 'react-apollo-hooks';
import CityList from './CityList';

const ALL_CITIES = gql`
  query {
    allCities {
      id
      name
      population
    }
  }
`;

const Sample = () => {
  const { data, error, loading } = useQuery(ALL_CITIES);
  if(loading) return <span>Loading...</span>
  if(error) return <span>error</span>
  return (
    <CityList cityList={data.allCities} />
  )
}

export default Sample;

@gql

GraphQL 쿼리를 간편하게 작성하게 해주는 컴포넌트입니다.

이 같은 작성 방식을 Tagged Template Literal이라고 합니다. 궁금하다면 아래 링크를 참고하세요.

※작성된 쿼리는 `Spring Boot & Graphql` 포스팅에서 PlayGround로 테스트했던 쿼리입니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals

 

Template literals

템플릿 리터럴은 내장된 표현식을 허용하는 문자열 리터럴입니다. 여러 줄로 이뤄진 문자열과 문자 보간기능을 사용할 수 있습니다. 이전 버전의 ES2015사양 명세에서는 "template strings" (템플릿 문자열) 라고 불려 왔습니다.

developer.mozilla.org

@useQuery

react-apollo의 Query를 Functional 하게 해주는 Hook입니다.

만일 react-apollo의 Query 컴포넌트를 사용하면 아래와 같습니다.

import React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
import CityList from './CityList';

const allCities = gql`
  query {
    allCities {
      id
      name
      population
    }
  }
`;

const Sample = () => {
  return (
    <Query query={allCities}> 
    {
      ({ loading, data, error }) => {
        if(loading) return <span>loading...</span>
        if(error) return <span>error</span>
        return (
          <CityList cityList={data.allCities} />
        )
      }
    }
    </Query>
  )
}

export default Sample;

# 실행

샘플 페이지까지 완성되었으니 테스트할 시간입니다.

$ npm start

만약 이전 포스팅에서 안내한 Free Template가 동일하게 적용되어있다면 아래와 같은 Home 화면이 나타납니다.


Sample 메뉴를 클릭한 뒤 아래처럼 정상적으로 샘플 데이터 목록이 나오는지 확인합니다.


# 마치며

이번 포스팅까지 Spring Boot & Graphql <> React-Apollo로 구성된 애플리케이션을 간단하게 구성해보았습니다.

[나만의 블로그]라는 이름에서 알 수 있듯이 이 애플리케이션의 콘셉트는 블로그입니다.

블로그는 검색 포털 사이트에서 검색했을 때 노출되어야 하겠죠. 그렇다면 결국 SEO가 필요합니다.

그래서 다음 포스팅에서는 SSR을 적용해보겠습니다.


# GitHub

https://github.com/eonnine/MyBlog.git

 

eonnine/MyBlog

Spring Boot, Graphql, PostgreSQL, React-Apollo, Parcel - eonnine/MyBlog

github.com

댓글