질문
Note that query keys act as dependencies for your query functions. Adding dependent variables to your query key will ensure that queries are cached independently, and that any time a variable changes, queries will be refetched automatically (depending on yourstaleTime
settings)
그전까지 내용 (제가 이해하기로) : queryFn이 어느 변수에 의존하면 그 변수가 queryKey에 있어야한다!
위의 내용 : queryKey에 종속 변수를 추가하면 쿼리가 독립적으로 캐시되는걸 보장하고, 변수가 바뀔때마다 쿼리가 자동적으로 refetch되는걸 보장한다
⇒ 질문 : 왜 “독립적으로” 캐시되는걸까요? 그게 무슨뜻일까요???
참여한 모임은 안에 연산이 있어서 캐싱써도 깜빠깅ㅁ..
궁금)
useMutation이 일어난걸 useQuery는 알지 못하나?
방법 1) useQuery의 refetch
방법 2) useMutation의 invalidateQueries
쿼리로불러온건- >refetch , 뮤테이션으로 갱신한건 mutate
3차
DetailPage state 위치 변경 & useQuery의 select 옵션
2 depth 만 내려가도 props drilling이 심하다고 한다.
그래서 post 에 관련된 컴포넌트 따로, comment 에 관련된 컴포넌트 따로 분리를 해서
드릴링을 완화해보고자 한다.
처음에는 아래 코드처럼 post 관련 컴포넌트를 묶어놓은 DeatailPost 컴포넌트에서
useQuery를 사용해서 전체 데이터를 받아놓는다.
// useQuery 함수 const usePostDetail = (postId: string) => { return useQuery({ queryKey: ['postDetail', postId], queryFn: async () => { return await getApi<IPost>(`/posts/${postId}`); }, enabled: !!postId, // Post의 많은 데이터들 중, 필요한 데이터만 가져오기 select: (data) => ({ likes: data.data.likes, _id: data.data._id, image: data.data.image, title: data.data.title, channel: data.data.channel, author: data.data.author, createdAt: data.data.createdAt, }), }); }; export const DeatailPost = () => { const id = useContext(PostIdContext); const { data } = usePostDetail(id); return ( <PostA id={id}/> <PostB id={id}/> <PostC id={id}/> ); }
이후, 각 컴포넌트에서 필요한 데이터를 useQuery 함수로 호출해서 사용하려 했다.
// PostA export const PostA = ({ id }) => { const { data } = usePostDetail(id); } // PostB export const PostB = ({ id }) => { const { data } = usePostDetail(id); } // PostC export const PostC = ({ id }) => { const { data } = usePostDetail(id); }
이때, 각 A B C 컴포너트에서 사용하는 데이터들은 모두 달랐다.
- PostA - likes, _id
- PostB - image, title
- PostC - author, createdAt
해서, 현재 캐싱되어 있는 전체 데이터 중에서
각 컴포넌트는 select 옵션을 사용하여 컴포넌트에 필요한 데이터만 호출하려 한다.
// A export const usePostA = (postId: string) => { return useQuery({ queryKey: ['postDetail', postId], queryFn: async () => { return await getApi<IPost>(`/posts/${postId}`); }, enabled: !!postId, select: (data) => ({ likes: data.data.likes, _id: data.data._id, }), }); }; // B export const usePostB = (postId: string) => { // ... 중복 생략 select: (data) => ({ image: data.data.image, title: data.data.title, }), }; // C export const usePostC = (postId: string) => { // ... 중복 생략 select: (data) => ({ author: data.data.author, createdAt: data.data.createdAt, }), };
위와 같은 경우, 컴포넌트가 많아지면 usePost… 의 수도 많아질 것이고, 이는 좋지 않은 현상이라 생각함.
때문에 처음 post / comment 를 나누는 상황에서만 select를 사용하고
이후에는 아래와 같이 사용하는 것을 생각하였음.
export const PostA = ({ id }) => { const { data } = usePostDetail(id); const dataA = { likes: data.data.likes, _id: data.data._id, }; } // PostB export const PostB = ({ id }) => { const { data } = usePostDetail(id); const dataB = { image: data.data.image, title: data.data.title, }; } // PostC export const PostC = ({ id }) => { const { data } = usePostDetail(id); const dataC = { author: data.data.author, createdAt: data.data.createdAt, }; }
useQuery의 select 옵션을 사용하기보다는, 사용하는 컴포넌트 쪽에서 명시적으로 다음과 같은 데이터를 사용할거다~ 를 기재해두는게 나을듯?
useMutation 옵션들
반환값
data
변이의 결과 데이터입니다.
error
변이 과정에서 발생한 오류 객체입니다.
isError
변이가 오류로 종료되었는지를 나타내는 부울 값입니다.
isIdle
변이가 아직 시작되지 않았음을 나타내는 상태입니다.
isPending
변이가 진행 중인지를 나타내는 상태입니다.
isPaused
변이가 일시 중지된 상태임을 나타냅니다.
isSuccess
변이가 성공적으로 완료되었음을 나타내는 부울 값입니다.
failureCount
변이가 실패한 횟수입니다.
failureReason
최근 변이 실패의 원인을 설명하는 값입니다.
mutate
변이 함수를 동기적으로 실행하는 함수입니다.
mutateAsync
변이 함수를 비동기적으로 실행하는 함수입니다.
reset
변이 상태를 초기화하는 함수입니다.
status
현재 변이의 상태를 나타내는 문자열(idle, pending, success, error 등).
submittedAt
변이가 마지막으로 실행된 시각입니다.
variables
변이를 실행할 때 사용된 변수들입니다.
옵션
onSuccess
API 호출이 성공했을 때 실행할 콜백 함수입니다.
여기서는 좋아요 상태를 업데이트하거나, 추가적인 데이터 캐시 로직을 추가할 수 있습니다.
onError
API 호출이 실패했을 때 실행할 콜백 함수입니다.
에러 처리 로직을 여기에 추가할 수 있습니다.
onSettled
성공이든 실패든 API 호출이 끝난 후에 실행할 콜백 함수입니다.
공통된 후처리 로직을 이곳에 배치할 수 있습니다. aka. finally
retry
실패했을 경우 자동으로 재시도할 횟수를 지정할 수 있습니다.
mutationKey
고유 식별자로서 캐싱 및 취소와 같은 작업을 특정할 때 사용됩니다.
mutationFn
실행할 비동기 함수입니다.
onMutate
변이가 트리거되기 직전에 호출되며, 현재 상태를 스냅샷하는 데 사용할 수 있습니다.
retryDelay
재시도 사이의 지연 시간을 지정할 수 있습니다.
useErrorBoundary
오류가 발생하면 자동으로 에러 경계를 트리거할지 여부를 결정합니다.
isEnabled
변이를 활성화 또는 비활성화 합니다.
gcTime
이 옵션은 컴포넌트에서 더 이상 활성화되지 않은 후 변이의 데이터를 캐시에서 얼마나 오랫동안 유지할지를 밀리초 단위로 지정합니다. 사용하지 않는 데이터가 메모리에 남아 있는 시간을 관리하여 메모리 사용을 최적화합니다.
networkMode
변이가 네트워크 및 캐싱 계층과 어떻게 상호 작용할지 구성합니다. always, offlineFirst 등의 값을 설정할 수 있어, 연결 상태와 캐시의 신선도에 기반하여 데이터 가져오기 전략을 최적화할 수 있습니다.
throwOnError
true로 설정하면, 변이 함수에서 발생한 오류를 재발생시킵니다. 이는 컴포넌트 내에서 try/catch 블록을 사용하여 오류를 로컬에서 처리하고자 할 때 유용합니다.
meta
변이 인스턴스에 임의의 메타데이터를 첨부할 수 있습니다. 이 메타데이터는 애플리케이션의 맥락에 관련된 어떤 것이든 될 수 있으며, 복잡한 애플리케이션에서 더 상세한 상태 추적이나 로깅을 위해 사용될 수 있습니다.
useMutation vs useMutationState
useMutation
훅은 일반적인 비동기 요청(주로 서버에 데이터를 생성, 업데이트, 삭제하는 작업)을 수행하는데 사용.useMutationState
훅은 MutationCache에 있는 mutation의 상태를 공유하고 다른 컴포넌트에서도 접근하기 위해사용4차
DetailPage 폴더 구조 변경 후 적용
📦DetailPage ┣ 📂components ┃ ┣ 📜FormatDate.ts ┃ ┣ 📜IsIComment.ts ┃ ┗ 📜PostIdContext.ts ┣ 📂DetailComment ┃ ┣ 📜CommentInput.tsx ┃ ┣ 📜CommentList.tsx ┃ ┗ 📜DetailComment.tsx ┣ 📂DetailPost ┃ ┣ 📜Badge.tsx ┃ ┣ 📜DetailPost.tsx ┃ ┣ 📜DetailTimeTablePage.tsx ┃ ┣ 📜PostArticle.tsx ┃ ┣ 📜PostContents.tsx ┃ ┗ 📜PostIcon.tsx ┣ 📂TimeTable ┃ ┣ ... ┣ 📂hooks ┃ ┗ 📜useVotingTimeTable.ts ┣ 📜DetailPage.tsx ┣ 📜PostHeader.tsx ┗ 📜PostTab.tsx
Suspense 규칙
예상 규칙
서버로부터 데이터를 받아온다.
이때, 본문이 로딩 중이라면?
⇒ 본문, 댓글 모두 로딩
(하나의 로딩 스피너)
댓글은 모두 받아와졌고, 본문만 로딩중일땐?
⇒ 댓글만 화면에 보이는 것은 별로라고 생각.
따라서 본문, 댓글 모두 로딩
(하나의 로딩 스피너)

이렇게 구상한 이유는 다음과 같다.
useQuery에는 select 라는 옵션이 있음. 모모 앱에서는 어쩔 수 없이 하나의 get 요청으로 본문, 댓글 2가지 종류의 데이터를 모두 불러온다. 다만, select를 활용하면 이를 분리할 수 있다고 판단했음. 따라서 본문 / 댓글 부분을 2개의 컴포넌트로 분리했고, 이에 맞게 로딩되는 타이밍에 맞춰 로딩 스피너를 다르게 화면에 출력하고 싶었음.
다만, 이는 구현하기 어려울 것 같다.
왜냐하면 select는
클라이언트
에서 실행되는 옵션이다.즉, 모든 데이터를 받아온 후 필터링을 거치는 것일 뿐, 원하는 데이터만 서버에서 받아오는 것이 아니다.
따라서 useQuery 한 번에 모든 데이터를 받아오는 것이므로, 본문 / 댓글 컴포넌트 모두 동시에 화면에 보여지게 될 것이다.
따라서 Suspense 등을 통한 규칙이나 로딩 스피너 타이밍은 설정하기 어려울 것 같다.
6차
Lighthouse 개선
전 )

후 )

SEO 향상 기록
Accessibility


- Image elements do not have
[alt]
attributes

이미지에 alt 속성 넣어주면 해결됨.
- Lists do not contain only <li> elements and script supporting elements (<script> and <template>).

ul 태그 내부에서, map으로 반복되는 공통 컴포넌트 Profile을 li 태그로 감싸줌으로 해결.
- Background and foreground colors do not have a sufficient contrast ratio.
- 배경색과 전경색(텍스트 색상) 간의 대비가 충분하지 않으면, 시각적으로 인식하기 어려울 수 있습니다. 특히 시각 장애가 있는 사용자에게 문제가 될 수 있습니다.
- 컬러 대비 비율을 최소 4.5:1 이상으로 설정하는 것이 일반적인 접근성 지침입니다. 이 비율은 작은 텍스트에 적용되며, 큰 텍스트(18pt 이상 또는 굵은 글씨 14pt 이상)의 경우 3:1 이상의 비율이 적용됩니다. WCAG(Web Content Accessibility Guidelines)에서 권장하는 대비 기준을 충족하는지 확인하기 위해 WebAIM's Contrast Checker 같은 도구를 사용할 수 있습니다.
- AA: 최소 조건 대비. 텍스트의 배경과 텍스트의 대비는 4.5:1이어야 한다. 단, 텍스트의 크기가 큰 경우에는 3:1의 대비만 충족되어도 된다. 텍스트가 단순히 페이지를 꾸미기 위한 용도이거나 로고나 브랜드 이름의 일부일 경우에는 대비 조건을 만족하지 않아도 된다.
- AAA: 최적 조건 대비. 배경과 텍스트가 7:1의 대비를 가져야 좋다. 단, 텍스트의 크기가 클 경우에는 4.5:1의 대비만 가져도 된다. AA와 마찬가지로, 단순 꾸미기 위한 텍스트나 로고나 브랜드 이름의 일부가 되는 텍스트는 대비 조건을 만족하지 않아도 된다.

우선, 유저 검색 부분.


Contrast 부분이 2.12로, 위와 같이 작은 텍스트의 경우 성공 기준이 4.5 이상이다.
이를 향상시켜 보자.
오프라인 상태의 유저를 cmd + shift + c로 styles를 살펴보자.

color 의 회색 네모 상자를 클릭해보면, 아래와 같은 창이 나타난다.

현재 우리 텍스트 색상의 대비는 2.12 이다.
아래에 AA와 AAA가 있는데, 이는 아래와 같다.
허나, 우리의 앱은 온/오프라인 상태의 유저를 구분하기 위해 현재 색상을 적용시켰다.
대비가 높은 색상을 적용시킨다면, 과연 유저가 온/오프라인임을 구분할 수 있을까?

위 사진처럼, 디스코드도 우리와 유사한 상황에서 대조가 1.71인 컬러를 사용하고 있다.
음… 차라리 온/오프라인 유저 텍스트 색상을 일치시키고, 이름 앞에나 뒤에 녹색, 적색 원을 붙여서 온/오프라인을 구분시키는건 어떨까 싶다.
SEO


- Links are not crawlable

문제가 발생한 컴포넌트는 모두 useNavigate() 를 통해 라우트를 이동시키고 있었음. 이를 react-router-dom의 Link element로 감싸주는 방식을 사용하여 문제를 해결함.
- robots.txt
robots.txt란?
검색 엔진 크롤러가 웹사이트를 어떻게 크롤링하고 인덱싱할지 지시하기 위한 파일
현재 모모 앱에서는 root directory에 robots.txt 파일을 생성만 해주니
lighthouse에서 robots.txt 관련 문제가 해결 되었다.
Fix 알림조회

notifications 요청이 들어가면서, 알림 목록에 있는 포스트들 하나하나에 다 요청을 보낸다!
여기서 문제는,
목록 중 읽음 처리한 알림인데도 조회를 하는것!

보지 않은(!seen)으로 필터링한 목록에 대해서만 get요청을 보내자!

++ 빈배열 처리!