1. 알림 페이지, 검색 페이지 퍼블리싱
우리 사이트는 경매 사이트로, 낙찰이 되거나 유찰이 되거나 했을 때 사용자에게 알림을 주는 기능이 있다. 오늘은 그 페이지를 작업했고 또 경매 물품을 검색할 수 있는 페이지 퍼블리싱 작업을 진행했다. 현재까지 기획된 내용이 최소 기획이기 때문에 빠르게 퍼블리싱을 끝내고 다음주부터 api 연결과 로직 구현에 들어가야한다.
오늘 작업한 내용, 별거 없지만 간단하게 코드 복기해보기.
일단 컴포넌트를 Page와 하위 컴포넌트로 분리하고 Page 컴포넌트에서 각 하위 컴포넌트에 필요한 데이터를 props로 넘겨주는 방식을 사용하고 있다. 사실 api를 어디에서 호출해서 관리할 것인지가 좀 고민이다. 페이지 컴포넌트를 분리해서 우리 사이트에 어떤 페이지가 있는지 한 눈에 알고싶은데, 전체 하위 컴포넌트에서 api를 각각 호출하자니 중복되는 코드가 너무 많아진다. 아무리 데이터 캐싱이 되고 요청이 중복되지 않는다고는 하나(리액트 쿼리), 호출 코드가 여기저기에 산재되어 있으니 코드 복잡도도 올라가고 관리에 어려움을 겪게 되는 것 같다.
아무튼 일단은 화면 구현을 위해 notificationResp 라는 임시 목데이터를 만들고 여기서 뿌려준다.
props를 받아와서 아래와 같이 렌더링을 해준다. 알림의 종류가 5가지 정도 있는데, 백엔드와 협의해서 이들을 number 형태의 상태 코드로 받아오고 프론트에서 각 상태를 상수로 관리하여 화면에 렌더링 해주고 있다.
지금은 아래와 같이 관리하고 있다. 상태에 관련된 내용은 다 number로 오고있어서, 순서를 맞춰서 배열에 저장했다. 지금은 일단 이 방식이 제일 간단하고 접근하기 쉬운데, 다른 문제점이 있는지 아직까진 파악되지 않는다.
이번에는 뷰랑 로직을 정말 열심히 분리하고 싶어서, delete 로직을 담당하는 함수도 훅을 통해서 받아오도록 분리했다.
api 연결은 나중에 한 번에 하려고 일단 만들어만 둠.
원티드 인턴쉽에서 뷰 로직 분리의 효용에 대해 배우게 된 뒤로(가독성이 좋아짐, 관심사가 분리되어 유지보수성 굿) 훅을 적극적으로 활용하려고 한다. 이렇게까지 하는게 맞는지 모르겠는데, 뭐 정답은 없는거니까 이번에도 적극 활용할 예정.
참, 검색 페이지 구현한 코드는 아래와 같다. 일단 페이지 컴포넌트. 카테고리와 키워드를 입력해서 쿼리를 만들어 요청보내는 컴포넌트, 그리고 인기 검색어 컴포넌트 두 가지로 나뉜다.
검색을 담당하는 SearchQuery 에 요청에 필요한 키워드와 카테고리 상태를 만들어서 search 해주는 함수에 전달한다.
이것도 역시 훅을 만들어서 따로 분리했다.
근데 일단 수도코드만 만들고, 엔더키 누르거나 돋보기 클릭했을 때 동작만 되도록 연결만 해둠... api 요청 어떻게 할건지 좀 생각해봐야겠다.
2. React Query
이번에 리액트 쿼리를 쓰기로 했는데, 지난번에 SWR을 사용하면서 api 요청에 대한 재사용 가능한 함수를 만들어놓지 않아서 코드가 중구난방이었다. 이번에는 api 폴더 내에서 객체 형태로 관리하자고 제안을 할 것 같다. 근데 리액트 쿼리를 어떻게 써야하는지 잘 몰라서 지금 블로그를 쓰면서 미리 공부를 좀 해보려고 함.(?)
아래 내용은 공식문서를 토대로 스스로 이해하기 쉬운 방법으로 정리된 내용이므로, 오역이나 틀린 개념이 있을 수 있음. 발견시 정정해주시면 큰 도움이 될 것 같습니다.
일단 React query 란 무엇이냐? 공식 문서에 가보면 "Performant and powerful data synchronization for React" 라고 대문짝만하게 써있다. 번역하면 성능 좋고 효율적인 리액트를 위한 데이터 동기화- 라고 한다.
데이터를 조회하고, 캐싱하고, 업데이트 하는 과정을 관리할 수 있는데, SWR 처럼 캐싱하면서 데이터 최신화하고 화면에 업데이트 하는 것을 간편하게 할 수 있도록 해주는 라이브러리 인 것 같다. 공식 문서에 따르면, 로컬 상태와 서버 상태를 분리시켜 관리해야할 필요성 그리고 그 관리의 복잡성 때문에 등장했다고 한다.
서버 상태란 로컬 상태의 반대 개념으로, 원격으로 존재해서 직접 컨트롤 할 수 없는 예를들면 비동기, api 호출을 통해 얻어지는 값을 의미한다. 값을 업데이트 하기 위해서 다시 호출이 필요하고, 동기화가 제대로 이루어지지 않으면 out-of-date 그러니까 낡은 데이터가 될 가능성이 있기 때문에 로컬 상태에 비해서 관리에 꽤 복잡한 코드가 필요하다.
리액트 쿼리는 서버상태 동기화의 번거로움을 해소하면서, 강력한 캐싱 기능을 제공해서 성능을 최적화시키고, out-of-date 상태인지를 판단해 이를 빠르게 업데이트 하는 기능을 가지고 있다. 서버 상태를 관리하기 위해 필요했던 많은 코드를 없애고 훅을 기반으로 하는 몇 줄의 코드만으로 쉽게 데이터를 가져오고, 캐싱하고, 업데이트 할 수 있다는 것이다.
3가지 코어 컨셉이 있다. Queries, Mutations, Query Invalidation.
Queries 는 해당 서버 데이터를 인식하기 위한 고유 키 값, 그리고 GET, POST 요청을 하는 함수와 함께 사용한다. 서버에서 데이터를 가져올 때 쓰는 것 같은데, 이 고유한 키 값을 토대로 재요청(refetching) 도 하고 캐싱도 하고, 전체 애플리케이션에서 쿼리 데이터에 접근을 가능케한다. SWR 에서 useFetch 를 사용하는 것 처럼, useQuery 라는 훅을 쓴다. url 대신 고유한 key 값으로 데이터를 분류한다는 것이 차이점인듯.
Mutations 는 query의 반대 개념으로, C U D 그러니까 서버쪽 데이터를 mutation - 변형할 때 사용된다. useQuery 처럼 useMutation이라는 훅을 쓴다.
Query Invalidation이란 개념은, 개발자가 직접 쿼리를 업데이트 할 때 사용하는 것 같다. 공식문서에 따르면 user의 행동으로 인해 어떤 쿼리가 최신 상태가 아닌 것을 인지했을 때, 백그라운드에서 비교해서 쿼리 데이터를 비교해서 음 오래되었군..하고 최신화를 하는 것보다, 그냥 바로 최신화를 시켜버리는게 더 나을 때가 있다는 것이다. QueryClient 라는 것에 invalidateQueries 라는 메서드를 통해서 이를 가능케 한다고 함.
그러니까 뭐 예를들면, 내가 어떤 댓글을 로컬에서 수정했는데, 수정한 액션이 일어났기 때문에 최신이 아니라는 것을 인지한 상태다 - 그러니까 이 메서드로 다시 쿼리를 업데이트 할 수 있다. 이런 건가?
일단 이렇게 개념만 보면 잘 모르겠으니까, 역시 공식문서에 나와있는 간단한 예제 코드를 뜯어보면서 좀 더 알아보자.
// 리액트 쿼리에서 필요한 훅과 컴포넌트를 가져오고 있다.
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from 'react-query'
// 뭔가 api 호출을 위한 추상화된 함수가 있나봄
import { getTodos, postTodo } from '../my-api'
// Create a client 클라이언트를 만들고 provider로 감싸서 제공해줌.
const queryClient = new QueryClient()
function App() {
return (
// Provide the client to your App
// 리코일이랑 비슷한 방법으로 적용한다.
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
function Todos() {
// Access the client
// useQueryClient 훅은 현재 queryClient의 인스턴스를 리턴한다.
const queryClient = useQueryClient()
// Queries
// 위에서 설명한 개념처럼, todos 라는 키로 쿼리를 가져왔다.
const query = useQuery('todos', getTodos)
// Mutations
// useMutate는 api 요청 함수와(promise반환!), api 요청의 결과에 따라 처리될 옵션 객체를 받는다.
// 두 번째 인자는 생략할 수 있음.
// useQuery와 비슷하게 로딩 여부, 데이터, 에러 등 다양한 데이터를 리턴한다.
const mutation = useMutation(postTodo, {
// postTodo에 성공하였을 경우
onSuccess: () => {
// 캐시 무효화 및 refetch를 진행하여 'todos'쿼리를 업데이트한다.
queryClient.invalidateQueries('todos')
},
})
return (
<div>
<ul>
{query.data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
<button
onClick={() => {
// mutate는 요청을 하는 메서드. postTodo api에서 사용할 인자값을 넣고,
// 두 번째 인자로 성공,실패, settled(finally같은것) 객체, 근데 생략 가능.
// 처음 useMutation으로 mutation 을 만들었을 때 두 번째 인자를 넣어줬다면
// useMutation의 옵션이 1번째 실행, mutate의 옵션이 2번째 실행
mutation.mutate({
id: Date.now(),
title: 'Do Laundry',
})
}}
>
Add Todo
</button>
</div>
)
}
render(<App />, document.getElementById('root'))
'프로젝트 > 데일리 옥션' 카테고리의 다른 글
# 작업일지 7 / 카운트다운 함수 만들기 (0) | 2023.02.13 |
---|---|
# 작업일지 6 / query parameter 에 접근하기 (0) | 2023.02.10 |
# 작업일지 4 / 모달창 작업, 캐러셀(?) 구현, relative absolute, 유니온타입 활용 (0) | 2023.02.08 |
# 작업일지 3 / Tailwind CSS 로 글로벌 스타일 적용하기 w.@layer가 뭐징 (0) | 2023.02.06 |
# 작업일지 2 / 프로젝트 셋팅, 작업 시작 (0) | 2023.02.04 |