일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 웹크롤링
- Redux
- component
- typescript
- styled-component
- npx
- socket.io
- route
- 정규표현식
- 성능최적화
- AWS
- Recoil
- CDN
- javascript animation
- express
- Modal
- docker
- go
- sequelize
- graphql
- 포트포워딩
- react
- 웹팩
- 반응형웹
- cicd
- 회고
- scrapping
- Today
- Total
프로그래밍 공부하기
GraphQL / Tour Sight Search 본문
우리나라 관광지 검색 서비스
프론트엔드: React-Hook, Apollo, GraphQL, styled-component
백엔드: GraphQL, REST API
사용 API: 한국 관광공사 Tour API 3.0
기능:
- 키워드 검색
- 관광지 좋아요 표시
GraphQL을 이용하여 간단한? 우리나라 관광지 검색 서비스를 만들어보았다. 처음 시작은 정말 간단히 끝날 것이라 생각했지만 GraphQL이 지난 년도에 바뀐 부분이 좀 있어서 공식문서 외의 구체적인 자료를 찾기가 상당히 어려웠다. 또한 리액트에 아직 익숙하지 않아서 고생했던 부분도 있었다. 하지만 계속 노력하니 내가 원하는대로 결국 만들어졌다! 물론 나중에 보면 내부코드나 디테일한 부분들이 맘에 안들 수도 있지만 일단은 만족스럽다. 이제 프로젝트의 구현과정을 기록해보려고 한다.
1. Wrapping REST API
프로젝트에서 가장 먼저 시작한 일은 REST API인 Tour API를 GraphQL로 감싼 백엔드 서버를 만드는 것이다. 해당 과정은 이 포스팅 에 자세히 기록되어 있다. 백엔드를 만들면서 GraphQL에서 자체적으로 Playground, GraphiQL과 같은 GUI 페이지를 제공한다는 것이 정말 편리했다. 내가 만든 서버를 GUI환경에서 테스트할 수 있고, 데이터 구조도 볼 수 있다는 것이 정말 큰 장점 같다.
2. React 앱 생성
React와 Apollo Graphql을 활용하여 내가 만든 서버에서 데이터를 요청하는 클라이언트를 생성하였다. 이 과정 역시 이 포스팅에 적혀있다! Client 쪽을 진행하며 지속적으로 나를 당황시켰던 점은 내가 참고로 하던 자료가 살짝 오래되서 현재 버전과 맞지 않는다는 점이었다. 이런 사항들을 해결할 때 나는 먼저 새롭게 업데이트 된 모듈을 설치하여 import-from문만 바꿔서 한 번 실행해 보고 안되면 공식문서를 찾는 방식을 사용한다. Apollog Graphql의 경우 공식문서가 친절한 편이라 코드를 이전버전에서 이후버전으로 migration 하는 방법이 써있는 경우가 종종 있었으며 많은 도움을 받았다!
3. styled-component 적용
import styled from "styled-components";
export const Thumbnail = styled.div`
width: 100%;
border-radius: 7px;
flex: 1 1 auto;
background-size: cover;
background-position: center center;
background-image: url(${props => props.bg});
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
`;
참고 자료를 보다가 styled-component라는 라이브러리를 알게되었다. 이전까지 컴포넌트를 꾸미기 위해 모든 컴포넌트의 CSS를 하나의 css 파일에 넣거나 컴포넌트 별로 css 를 만들었다. 즉 CSS와 JavaScript 파일을 별개로 사용해왔던 것이다. 그러나 styled-component를 사용하여 어떤 디자인을 갖춘 div, h1 등의 요소를 만들고, 이를 React에서 렌더링할 때 해당 이름을 사용하고 다른 곳에서 재사용할 수 있었다. 또한 나의 경우 앱 전체를 둘러싼 Container와 관광지 이름+썸네일을 둘러싼 Container가 있었는데 만일 하나의 css 파일을 사용했다면 이 둘을 구별하기위한 이름 짓기가 쉽지 않았을 것이다. 그러나 styled-component를 이용하여 두 요소를 Container라고 똑같이 이름 짓고 각자 다른 파일에서 styled-component를 import 하여 사용하는 것으로 이러한 문제를 해결할 수 있었다. 게다가 styled-component는 javascript이기 때문에 컴포넌트로 부터 css로 썸네일 이미지를 전달할 수도 있었다.
4. Re-Query
사용자가 새로운 키워드 검색을 수행하거나 페이지를 넘길 때 새로운 쿼리를 수행해주어야 한다. 나는 이 부분을 당연히 쿼리를 수행하는 함수를 별도로 만들어서 이를 호출해야 한다고 생각했다. 그러나 함수로 만들어서 이를 수행하였을 때 갖가지 오류만 잔뜩 발생하였다. 오류가 발생하면 해당 오류를 검색하여 고치고, 이를 다시 수행하면 다시 새로운 오류가 발생하여 이를 고치고... 의 반복이었다. 이대로는 도저히 안되겠다 생각하여 다시 쿼리를 수행할 방법에 대해 처음부터 다시 생각해보았는데, 혹시 새로운 키워드 검색이나 페이지를 넘길 때 State가 변하므로 재 렌더링 되니까 쿼리도 다시 수행되는 것이 아닐까? 라는 생각이 들어 재 쿼리에 대한 모든 내용을 지우고 변수 부분만 조금 다듬었더니 성공하였다!! 지금 생각해보면 왜 이걸 생각 못 했지? 싶지만 그 때는 정말 심각했다... 진행이 너무 안될 때는 조금 떨어져서 다시 생각해보는 시간을 갖는 것도 필요한 것 같다.
5. Session
원래는 세션을 생각하지 않고 모든 정보를 컴포넌트의 state에 저장하려고 하였다. 그런데 관광지 썸네일 클릭 시 Detail 페이지로 넘어가고, 다시 뒤로가기를 하면서 컴포넌트들이 재 렌더링이 되며 state가 유지되지 않는 현상을 발견하였다. 대표적으로 입력 키워드가 뒤로가기를 할 때 유지되지 않았다. 그래서 방법을 찾아보니 History, Storage, Redux라는 키워드들을 발견할 수 있었다. 그 중 나는 Session Storage를 사용하기로 하였다. Session Storage는 현재 창이 열려있는 한 저장된 데이터를 유지하는 저장소이므로 키워드를 저장하기에 딱 맞는 방법이라 생각하였기 때문이다.
6. Mutation
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache({
typePolicies: {
SightList: {
keyFields: [],
},
Sight: {
keyFields: ["contentid"]
}
}
}),
resolvers: {
Sight: {
isLiked: () => false
},
Mutation: {
toggleLikeSight: (_, { id, isLiked }, { cache }) => {
const mySight = {
__typename: 'Sight',
contentid: id,
};
cache.modify({
id: cache.identify(mySight),
fields: {
isLiked(cachedName) {
return !isLiked;
}
}
});
}
}
}
});
마지막으로 썸네일 상단의 하트를 눌러 '좋아요' 를 표시할 수 있는 기능을 작성하였다. 해당 이미지는 css에서 display: absolute 를 사용하여 원하는 위치에 놓을 수 있었다. 그 후 Client 에서만 저장하고 있는 isLiked라는 속성을 GraphQL의 캐시에 등록하는 과정을 수행하였다. 사실 이 부분이 프로젝트에서 가장 어려운 부분이었다. 먼저 참고 자료가 예전 코드라 사용할 수 없었고, 새로운 방식을 찾았지만 생각처럼 잘 되지 않았기 때문이다. 가장 문제가 된 부분은 cache.identify라는 부분이었다. GraphQL은 Query된 결과 객체들을 캐시에 저장하고 있는데, 사용자가 하트 이미지를 누를 때 해당하는 객체를 캐시에서 찾아 isLiked 필드의 값을 반전시켜야했다. 그런데 해당하는 객체를 찾지 못하는 문제가 발생한 것이다. 이를 해결하기 위해 공식문서를 뒤지다가 customizing identifier 라는 항목을 발견하였다. GraphQL은 나름의 방식으로 쿼리 결과들을 구분하고 있는데 구분하는 키를 사용자가 지정해줄 수 있다는 것이다. 나는 위와 같이 Sight 타입은 contentid라는 필드로 구분하도록 지정해주어 이를 해결할 수 있었다.
'Project > Practice' 카테고리의 다른 글
TypeScript / Project Timer (0) | 2021.04.05 |
---|---|
socket.io / 실시간 채팅 (0) | 2021.02.09 |
JSAnimation / BrawlStars GunFight! (0) | 2021.01.18 |
DOM / Twittller (0) | 2020.12.30 |
JS / 계산기 (0) | 2020.12.17 |