Skip to content

fix: 초기 로딩 스켈레톤/무한스크롤 로딩 분리#314

Open
heeeeyong wants to merge 1 commit intorefactorfrom
refactor-infiniteScroll
Open

fix: 초기 로딩 스켈레톤/무한스크롤 로딩 분리#314
heeeeyong wants to merge 1 commit intorefactorfrom
refactor-infiniteScroll

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Feb 25, 2026

📝작업 내용

Memory, SearchBook, SavePage, GroupSearch, FollowerListPage, Feed, FeedDetailPage에서 훅 기반 무한스크롤, 기존 수동 페이징, 스켈레톤 로직이 혼재되어 있어 아래 원칙에 따라 코드를 정리했습니다.

  • useInifinieScroll만 남기고 수동 loadMore/observer 제거
  • 스켈레톤은 isLoading && items.length === 0에서만 표시
  • 무한스크롤 추가 로딩(isLoadingMore)에서는 스켈레톤 미사용 (필요시 스피너만 유지)
  • 충돌로 생긴 중복 요청, 미정의 변수, 잘못된 import 정리

수정 파일

  • src/pages/feed/Feed.tsx
    • 섞여 있던 setTabLoading/loadTotalFeeds/loadMyFeeds 경로 제거
    • currentFeed.isLoading && currentFeed.items.length === 0일 때만 초기 스켈레톤
    • 무한스크롤은 useInifinieScroll 결과만 사용
  • src/pages/feed/FeedDetailPage.tsx
    • 중복 호출/깨진 로딩 코드 정리
    • 상세 데이터 초기 로딩만 스켈레톤 유지
  • src/pages/feed/FollowerListPage.tsx
    • 수동 페이징 코드 제거하고 useInifinieScroll로 단일화
    • 초기 진입만 UserProfileItemSkeleton 표시
  • src/pages/groupSearch/GroupSearch.tsx
    • 깨진 검색/페이징 병합 코드 정리
    • 검색 결과도 훅 기반으로 통일
    • 초기 검색 로딩에만 카드 스켈레톤
  • src/pages/memory/Memory.tsx
    • 충돌 상태였던 파일을 전체 정리
    • 초기 진입 시만 RecordItemSkeleton
    • 무한스크롤 시 스켈레톤 재표시 없이 리스트 유지
  • src/pages/mypage/SavePage.tsx
    • 기존 수동 IntersectionObserver/중복 상태 제거
    • 탭별 훅 결과 기준으로 초기 스켈레톤만 표시
  • src/pages/searchBook/SearchBook.tsx
    • 수동 loadFirst/loadMore 제거, 훅 기반으로 통일
    • 초기 피드 로딩에서만 FeedPostSkeleton
    • LoadingBox import 누락 포함 정리

Summary by CodeRabbit

릴리스 노트

  • 성능 개선

    • 피드, 팔로워 목록, 검색 등 여러 페이지에서 무한 스크롤 로딩 최적화
    • 초기 로딩 상태 및 스켈레톤 렌더링 개선으로 더 부드러운 사용자 경험 제공
  • 버그 수정

    • 메모리 페이지에서 특정 오류 발생 시 필터 초기화 및 개선된 오류 메시지 추가
    • 검색 기능의 디바운싱 로직 개선으로 응답성 향상

@heeeeyong heeeeyong self-assigned this Feb 25, 2026
@vercel
Copy link

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thip Ready Ready Preview, Comment Feb 25, 2026 9:17pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

여러 페이지에서 데이터 로딩 및 무한 스크롤 로직을 useInfiniteScroll 훅으로 통합하는 리팩토링입니다. 피드, 팔로워 목록, 그룹 검색, 저장된 컨텐츠, 메모리 페이지에서 기존의 수동 페이지네이션 로직을 선언적 훅 기반 접근 방식으로 대체합니다.

Changes

코호트 / 파일(들) 요약
피드 페이지
src/pages/feed/Feed.tsx, src/pages/feed/FeedDetailPage.tsx
Feed.tsx에서 로딩 오케스트레이션 제거 후 파생 showInitialLoading 플래그로 스켈레톤 렌더링 제어. TotalFeed의 isMyFeed prop을 isTotalFeed로 변경. FeedDetailPage.tsx에서 댓글 가져오기 제거 및 피드 세부 정보 가져오기만 간소화.
무한 스크롤 훅 도입
src/pages/feed/FollowerListPage.tsx, src/pages/groupSearch/GroupSearch.tsx, src/pages/mypage/SavePage.tsx, src/pages/searchBook/SearchBook.tsx
전체 페이지에서 useInfiniteScroll 훅으로 수동 페이지네이션 로직 교체. FollowerListPage에서 커스텀 로드 가능 로직을 훅 기반으로 재작성. GroupSearch에서 디바운스 검색어 및 쿼리 기반 검색으로 변경. SavePage에서 통합 피드/도서 무한 스크롤 구현. SearchBook에서 fetchPage 콜백으로 정규화된 페이지 구조 반환.
메모리 페이지
src/pages/memory/Memory.tsx
메모리 포스트 로딩 리팩토링으로 별도 loadMemoryPosts 제거 및 recordsList fetchPage 내에 통합. 읽기 진행 제약 위반 시 에러 처리 강화 (40002 에러 코드). 페이지네이션 타이밍에만 최소 로딩 시간 적용. 스켈레톤 렌더링 및 스크롤 센티널 로직 조정.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • THIP-Web#310: 여러 컴포넌트(Feed.tsx, FeedDetailPage.tsx 등)를 새로운 useInfiniteScroll 훅으로 마이그레이션하는 직접 관련 PR
  • THIP-Web#162: SearchBook.tsx의 피드 로딩 로직에서 수동 페이지네이션을 useInfiniteScroll으로 교체하는 동일 코드 영역 수정
  • THIP-Web#176: Feed.tsx의 로딩 오케스트레이션 변경(loadFeedsWithToken/tabLoading 제거 후 showInitialLoading 도입)과 직접 관련

Suggested reviewers

  • ljh130334
  • ho0010

Poem

🐰 무한 스크롤의 춤을 추며,
훅이 된 페이지들 손을 잡고,
로딩 상태는 깔끔하게,
선언적 흐름으로 우아하게,
코드는 더욱 명확해졌네! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경 사항을 명확하게 요약하고 있습니다. 초기 로딩 스켈레톤과 무한스크롤 로딩을 분리하는 것이 이 PR의 핵심 목표이며, 제목이 이를 정확하게 반영합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-infiniteScroll

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/pages/mypage/SavePage.tsx (1)

38-66: ⚠️ Potential issue | 🟡 Minor

Feed.tsx와 동일하게 minLoadingTime 패턴이 빠져 있습니다. FollowerListPage, SearchBook, Memory 등 다른 페이지에서는 첫 페이지 로딩 시 500ms 최소 지연을 적용하고 있으나, 여기에는 없습니다. 빠른 네트워크에서 스켈레톤 깜빡임이 발생할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/mypage/SavePage.tsx` around lines 38 - 66, The savedFeeds and
savedBooks useInifinieScroll calls are missing the minLoadingTime pattern which
causes skeleton flicker on fast networks; update both useInifinieScroll
invocations (the ones that define fetchPage for getSavedFeedsInMy and
getSavedBooksInMy) to include a minLoadingTime option of 500 (apply it so it
affects initial load — i.e., when cursor is null/undefined) following the same
pattern used in Feed.tsx/Followers/SearchBook/Memory to ensure the first-page
loading shows at least 500ms.
src/pages/feed/FeedDetailPage.tsx (1)

137-145: ⚠️ Potential issue | 🟡 Minor

window.close()가 중복 호출됩니다.

Line 138에서 window.close()가 무조건 호출된 후, Line 141에서 다시 호출됩니다. 첫 번째 호출 이후 if/else 블록은 사실상 도달할 수 없는 코드입니다. 이 PR에서 도입된 변경은 아니지만, 의도한 동작은 window.opener가 있을 때만 window.close()하고 그렇지 않으면 navigate(-1)하는 것으로 보입니다.

🐛 수정 제안
 const handleBackClick = () => {
-  window.close();
-
   if (window.opener) {
     window.close();
   } else {
     navigate(-1);
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/FeedDetailPage.tsx` around lines 137 - 145, The
handleBackClick function currently calls window.close() unconditionally and
again inside the if block, making the first call redundant; update
handleBackClick so it only checks window.opener and calls window.close() when
window.opener is truthy, otherwise call navigate(-1). Locate the function by
name handleBackClick and adjust the conditional logic (referencing window.opener
and navigate) to remove the duplicate unconditional window.close() call.
src/pages/feed/Feed.tsx (1)

43-74: ⚠️ Potential issue | 🟡 Minor

fetchPage에 500ms 최소 로딩 시간이 없습니다.

다른 페이지들(FollowerListPage, SearchBook, Memory 등)은 첫 페이지 로딩 시 minLoadingTime(500ms)을 적용하여 스켈레톤이 너무 짧게 깜빡이는 것을 방지하고 있습니다. Feed.tsxtotalFeedmyFeedfetchPage에는 이 패턴이 빠져 있어, 빠른 네트워크 환경에서 스켈레톤이 순간적으로 깜빡일 수 있습니다.

Based on learnings, Feed 페이지 초기 로딩 지연은 500ms여야 합니다.

🔧 수정 제안 (totalFeed 예시, myFeed에도 동일 적용)
   const totalFeed = useInifinieScroll<PostData>({
     enabled: activeTab === '피드',
     reloadKey: activeTab,
     fetchPage: async cursor => {
       await waitForToken();
+      const minLoadingTime = cursor ? null : new Promise(resolve => setTimeout(resolve, 500));
       const response = await getTotalFeeds(cursor ? { cursor } : undefined);
+      if (minLoadingTime) await minLoadingTime;
       return {
         items: response.data.feedList,
         nextCursor: response.data.nextCursor || null,
         isLast: response.data.isLast,
       };
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/feed/Feed.tsx` around lines 43 - 74, The fetchPage implementations
in totalFeed and myFeed must enforce a 500ms minimum loading time for the
initial page load to prevent skeleton flicker: inside each fetchPage (in the
useInifinieScroll calls for totalFeed and myFeed) record start time before
awaiting waitForToken(), run the network request (getTotalFeeds/getMyFeeds) and
in parallel await a delay so that when cursor is falsy (initial load) the total
elapsed time is at least 500ms, then return the response as before; implement
the delay by computing remaining = 500 - (Date.now() - start) and awaiting a
sleep/timeout only if remaining > 0, leaving non-initial pages unchanged.
🧹 Nitpick comments (5)
src/pages/groupSearch/GroupSearch.tsx (2)

44-48: fetchRecentSearches가 useEffect 의존성 배열에 누락되었습니다.

Line 41의 마운트 effect에는 eslint-disable 주석이 있지만, 이 effect(Line 44-48)에서는 fetchRecentSearches를 호출하면서 의존성 배열에 포함하지 않았습니다. useCallback으로 감싸져 있어 현재는 안정적인 참조이지만, fetchRecentSearches를 의존성에 추가하는 것이 좋습니다.

♻️ 수정 제안
   useEffect(() => {
     if (searchStatus === 'idle') {
       fetchRecentSearches();
     }
-  }, [searchStatus]);
+  }, [searchStatus, fetchRecentSearches]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/groupSearch/GroupSearch.tsx` around lines 44 - 48, The effect that
calls fetchRecentSearches when searchStatus === 'idle' is missing
fetchRecentSearches from its dependency array; update the useEffect to include
fetchRecentSearches (i.e., useEffect(..., [searchStatus, fetchRecentSearches]))
and ensure the fetchRecentSearches function is memoized with useCallback (or
remains so) to provide a stable reference; also remove any eslint-disable
comment masking this dependency if present so the linter can validate the
dependency list.

139-141: reloadKeysearchStatus가 포함되어 있어 불필요한 재요청이 발생할 수 있습니다.

searchStatus'searching''searched'로 변경될 때 queryTerm도 함께 변경되면, reloadKeysearchStatusqueryTerm 두 값이 모두 바뀌면서 effect가 두 번 트리거될 수 있습니다. 또한 searchStatusisFinalized 파라미터에도 영향을 주므로, reloadKey에서 searchStatus를 제거하면 실제 검색 파라미터 변화에만 반응하도록 할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/groupSearch/GroupSearch.tsx` around lines 139 - 141, The reloadKey
currently includes searchStatus which causes redundant reloads when status flips
(e.g., 'searching'→'searched'); update the invocation of useInifinieScroll by
removing searchStatus from the reloadKey (keep
reloadKey=`${queryTerm}-${selectedFilter}-${category}`) so the effect only
reacts to actual search parameters, but leave the enabled logic (enabled:
searchStatus !== 'idle' && (searchStatus === 'searched' || !!queryTerm)) and any
isFinalized handling unchanged.
src/pages/mypage/SavePage.tsx (1)

118-191: 렌더링 로직의 중첩 삼항 연산자가 깊습니다.

showInitialLoading ? (...) : activeTab === '피드' ? (...) : savedBooks.items.length > 0 ? (...) : (...) 처럼 4단계 깊이의 삼항 연산자 체인은 가독성을 떨어뜨립니다. 각 조건별 렌더링을 별도 컴포넌트나 함수로 추출하면 유지보수성이 개선될 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/mypage/SavePage.tsx` around lines 118 - 191, The JSX uses a deeply
nested ternary (showInitialLoading ? ... : activeTab === '피드' ? ... :
savedBooks.items.length > 0 ? ... : ...) which hurts readability; refactor by
extracting each branch into small components or functions (e.g.,
LoadingSkeletons using FeedPostSkeleton and BookSkeletonItem, SavedFeedList
rendering savedFeeds.items with FeedPost and handleFeedSaveToggle, and
SavedBookList rendering savedBooks.items with Cover, BookInfo and
handleSaveToggle) and replace the ternary chain in SavePage's render with a
clear conditional (if/switch or direct component returns) that returns
LoadingSkeletons, SavedFeedList, SavedBookList, or EmptyState accordingly;
ensure you keep sentinelRef, isLoadingMore, keys (feed.feedId, book.bookId) and
props like isLast/isMyFeed when moving logic.
src/pages/memory/Memory.tsx (2)

207-221: 서버 필터링과 클라이언트 필터링이 중복됩니다.

fetchPage에서 이미 isOverview, pageStart, pageEnd, isPageFilter 파라미터를 API에 전달하여 서버 측에서 필터링하고 있습니다(Lines 114-120). 그런데 filteredRecords(Lines 207-221)에서 동일한 조건으로 다시 클라이언트 필터링을 수행하고 있습니다. 서버 응답이 이미 필터링된 데이터를 반환한다면 이 메모이제이션은 불필요한 연산입니다.

서버 필터링만으로 충분하다면 filteredRecords를 제거하고 recordsList.items를 직접 사용하는 것을 고려해주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/memory/Memory.tsx` around lines 207 - 221, The client-side
filtering in the useMemo named filteredRecords duplicates server-side filtering
done in fetchPage (parameters isOverview, pageStart, pageEnd, isPageFilter);
remove filteredRecords and replace its usages with recordsList.items (or change
the memo to simply return recordsList.items) so we rely on server-filtered
results; ensure any references to activeFilter/selectedPageRange that expected
client-side filtering are updated to trigger fetchPage instead and that UI logic
still behaves correctly.

143-151: Axios 에러 타입 체크가 취약합니다.

덕 타이핑으로 'response' in error를 확인하는 대신 axiosisAxiosError 유틸리티나 instanceof AxiosError를 사용하면 타입 안전성이 향상됩니다. 관련 코드 스니펫(getRoomPlaying.ts)에서도 instanceof AxiosError를 사용하고 있습니다.

♻️ 수정 제안
+      import { AxiosError } from 'axios';
       // ...
       } catch (error) {
-        if (error && typeof error === 'object' && 'response' in error) {
-          const axiosError = error as { response?: { data?: { code?: number } } };
-          if (axiosError.response?.data?.code === 40002) {
+        if (error instanceof AxiosError && error.response?.data?.code === 40002) {
             setActiveFilter(null);
             throw new Error('독서 진행률이 80% 이상이어야 총평을 볼 수 있습니다.');
-          }
         }
         throw new Error('기록을 불러오는 중 오류가 발생했습니다.');
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/memory/Memory.tsx` around lines 143 - 151, Replace the duck-typing
error check in the catch block of Memory.tsx with axios' type-safe check: import
and use axios.isAxiosError (or AxiosError instanceof) to determine if the thrown
error is an AxiosError, then inspect axiosError.response?.data?.code === 40002
and call setActiveFilter(null) and rethrow the specific 80% message; otherwise
rethrow the generic "기록을 불러오는 중 오류가 발생했습니다." error—update the catch branch that
currently uses "'response' in error" and the axiosError type assertion to use
the axios utility or AxiosError type for proper type safety.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/pages/feed/Feed.tsx`:
- Around line 43-74: The fetchPage implementations in totalFeed and myFeed must
enforce a 500ms minimum loading time for the initial page load to prevent
skeleton flicker: inside each fetchPage (in the useInifinieScroll calls for
totalFeed and myFeed) record start time before awaiting waitForToken(), run the
network request (getTotalFeeds/getMyFeeds) and in parallel await a delay so that
when cursor is falsy (initial load) the total elapsed time is at least 500ms,
then return the response as before; implement the delay by computing remaining =
500 - (Date.now() - start) and awaiting a sleep/timeout only if remaining > 0,
leaving non-initial pages unchanged.

In `@src/pages/feed/FeedDetailPage.tsx`:
- Around line 137-145: The handleBackClick function currently calls
window.close() unconditionally and again inside the if block, making the first
call redundant; update handleBackClick so it only checks window.opener and calls
window.close() when window.opener is truthy, otherwise call navigate(-1). Locate
the function by name handleBackClick and adjust the conditional logic
(referencing window.opener and navigate) to remove the duplicate unconditional
window.close() call.

In `@src/pages/mypage/SavePage.tsx`:
- Around line 38-66: The savedFeeds and savedBooks useInifinieScroll calls are
missing the minLoadingTime pattern which causes skeleton flicker on fast
networks; update both useInifinieScroll invocations (the ones that define
fetchPage for getSavedFeedsInMy and getSavedBooksInMy) to include a
minLoadingTime option of 500 (apply it so it affects initial load — i.e., when
cursor is null/undefined) following the same pattern used in
Feed.tsx/Followers/SearchBook/Memory to ensure the first-page loading shows at
least 500ms.

---

Nitpick comments:
In `@src/pages/groupSearch/GroupSearch.tsx`:
- Around line 44-48: The effect that calls fetchRecentSearches when searchStatus
=== 'idle' is missing fetchRecentSearches from its dependency array; update the
useEffect to include fetchRecentSearches (i.e., useEffect(..., [searchStatus,
fetchRecentSearches])) and ensure the fetchRecentSearches function is memoized
with useCallback (or remains so) to provide a stable reference; also remove any
eslint-disable comment masking this dependency if present so the linter can
validate the dependency list.
- Around line 139-141: The reloadKey currently includes searchStatus which
causes redundant reloads when status flips (e.g., 'searching'→'searched');
update the invocation of useInifinieScroll by removing searchStatus from the
reloadKey (keep reloadKey=`${queryTerm}-${selectedFilter}-${category}`) so the
effect only reacts to actual search parameters, but leave the enabled logic
(enabled: searchStatus !== 'idle' && (searchStatus === 'searched' ||
!!queryTerm)) and any isFinalized handling unchanged.

In `@src/pages/memory/Memory.tsx`:
- Around line 207-221: The client-side filtering in the useMemo named
filteredRecords duplicates server-side filtering done in fetchPage (parameters
isOverview, pageStart, pageEnd, isPageFilter); remove filteredRecords and
replace its usages with recordsList.items (or change the memo to simply return
recordsList.items) so we rely on server-filtered results; ensure any references
to activeFilter/selectedPageRange that expected client-side filtering are
updated to trigger fetchPage instead and that UI logic still behaves correctly.
- Around line 143-151: Replace the duck-typing error check in the catch block of
Memory.tsx with axios' type-safe check: import and use axios.isAxiosError (or
AxiosError instanceof) to determine if the thrown error is an AxiosError, then
inspect axiosError.response?.data?.code === 40002 and call setActiveFilter(null)
and rethrow the specific 80% message; otherwise rethrow the generic "기록을 불러오는 중
오류가 발생했습니다." error—update the catch branch that currently uses "'response' in
error" and the axiosError type assertion to use the axios utility or AxiosError
type for proper type safety.

In `@src/pages/mypage/SavePage.tsx`:
- Around line 118-191: The JSX uses a deeply nested ternary (showInitialLoading
? ... : activeTab === '피드' ? ... : savedBooks.items.length > 0 ? ... : ...)
which hurts readability; refactor by extracting each branch into small
components or functions (e.g., LoadingSkeletons using FeedPostSkeleton and
BookSkeletonItem, SavedFeedList rendering savedFeeds.items with FeedPost and
handleFeedSaveToggle, and SavedBookList rendering savedBooks.items with Cover,
BookInfo and handleSaveToggle) and replace the ternary chain in SavePage's
render with a clear conditional (if/switch or direct component returns) that
returns LoadingSkeletons, SavedFeedList, SavedBookList, or EmptyState
accordingly; ensure you keep sentinelRef, isLoadingMore, keys (feed.feedId,
book.bookId) and props like isLast/isMyFeed when moving logic.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f9b157 and a6b1182.

📒 Files selected for processing (7)
  • src/pages/feed/Feed.tsx
  • src/pages/feed/FeedDetailPage.tsx
  • src/pages/feed/FollowerListPage.tsx
  • src/pages/groupSearch/GroupSearch.tsx
  • src/pages/memory/Memory.tsx
  • src/pages/mypage/SavePage.tsx
  • src/pages/searchBook/SearchBook.tsx

Copy link
Member

@ljh130334 ljh130334 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

충돌 많았을텐데 깔끔하게 작업해주셔서 감사합니다!! 넘 수고하셨습니다 👍🏻 🥇

Copy link
Collaborator

@ho0010 ho0010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨습니다!
확실히 로직을 분리하니 가독성 측면에서 크게 개선된 것 같네요~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants