2026.09.02 pl미팅 피드백 기반 추가학습 - 100-hours-a-week/KTB4-3rd-wiki GitHub Wiki
Ts
ts와 js 차이
- 실행 방식
- js는 바로 node.js나 브라우저에서 런타임으로 실행됩니다.
- ts는 바로 실행할 수 없기 때문에 빌드 과정에서 타입 검사를 실행하고 js로 트랜스파일된 뒤에 실행됩니다.
- 문법 확장
- ts는 type, interface, enum, generic, 접근 제어자같은 문법이 있습니다.
- 컴파일 단계 검증
- ts는 타입 정보를 기반으로 존재하지 않는 객체 속성이나 잘못된 타입 전달 같은 오류를 빌드 시점에 발견할 수 있습니다.
- 빌드 설정
- ts를 사용하면 tsconfig.json 설정파일을 따로 작성해야합니다.
- 해당 파일에는 제외/포함할 파일, 어떤 js 버전으로 변환할지, 모듈 방식 등에 대해 적어놓습니다.
- 타입정보 제공
- ts는 타입정보를 제공해주기때문에 IDE에서 자동완성, 타입 오류 표시를 지원해줍니다.
- 정적 타입정보를 통해 더 안전한 리팩토링이 가능하고 사용처 추적도 js를 쓸때보다 정확도가 높아집니다.
type과 interface 차이
- type은 객체뿐 아니라 원시 타입 별칭, 유니온 타입, 튜플, 조건부 타입 등 다양한 타입을 표현할 수 있습니다.
- 반면 interface는 주로 객체의 구조나 클래스가 구현해야 하는 계약을 정의하는 데 사용됩니다.
- 또한 interface는 선언 병합이 가능하고, 확장할 때 extends를 사용합니다.
- type은 선언 병합은 지원하지 않지만 유니온이나 인터섹션 타입을 활용할 수 있습니다.
any와 unknown 차이
- any는 타입 검사를 아예 실행하지 않습니다.
- unknown은 연산을 하기 전에 해당 값의 타입 체크를 요구합니다.
- 따라서 unknown이 any보다 안전합니다.
Next
React와 Next의 관계
React는 웹 ui 라이브러리입니다. next.js는 react 기반의 웹 프레임워크입니다. Next는 react의 기능을 기본적으로 제공하며, 다음과같은 기능들을 추가로 제공합니다.
- 다양한 렌더링 방식 (SSR, SSG, ISR)
- 서버 컴포넌트 / 서버 액션
- 이미지/폰트 최적화
- 파일 기반 라우팅
- 코드 스플리팅
- 빌드와 배포 구조
react만 사용할때는 개발자가 별도의 라이브러리와 설정을 통해 구성해야하지만, Next를 사용하면 이런 기능들을 통합된 방식으로 사용가능합니다.
각 렌더링 방식
SSR(Server Side Rendering)
- 사용자가 요청을 보낼 때마다 서버에서 html을 생성해 클라이언트로 보내주는 방식입니다.
- Next.js App Router에서는 일반적으로 이를 Dynamic Rendering이라고 표현합니다.
SSG(Static Site Generation) 정적 사이트 생성
- 빌드할때 html을 만들어놓는 방식
- 정적인 페이지(블로그, 뉴스페이지 등)에 적합합니다.
- 미리 만들어놓은 페이지를 불러오기때문에 로딩속도가 빠르다는 장점이 있습니다.
ISR(Incremental Static Regeneration)
- 빌드시점에 생성한 페이지를 제공하다가, 일정 시간이 지나면 백그라운드에서 새로 페이지를 생성해 제공하는 방식
- 실시간 데이터는 아니지만 자주 업데이트되어야하는 페이지에 적합합니다. next.js 공식문서
서버 컴포넌트와 클라이언트 컴포넌트
-
서버 컴포넌트
- 서버에서 실행되는 컴포넌트입니다.
- Next.js App Router에서는 layout과 page가 기본적으로 서버 컴포넌트입니다.
- 데이터 가져오기, API키/토큰같은 보안정보 등을 다루는 컴포넌트에 사용하기 적합합니다.
- 서버 컴포넌트 코드는 js 번들에 포함되지 않습니다.
- 브라우저 api나 react hook 등은 서버컴포넌트에서 사용불가합니다.
-
클라이언트 컴포넌트
- 클라이언트에서 실행되는 컴포넌트입니다.
- 상태관리, 이벤트처리 등 유저 상호작용이 필요한 부분은 클라이언트 컴포넌트를 사용해야합니다.
- window, localstorage 등 브라우저 api를 사용해야할때도 클라이언트 컴포넌트를 사용해야합니다.
- Next.js app router에서는 기본적으로 서버컴포넌트이기때문에, 클라이언트 컴포넌트를 사용하려면 파일 최상단에 'use client'를 작성해야합니다.
서버 컴포넌트와 클라이언트 컴포넌트의 동작 과정
- 서버에서는 서버 컴포넌트를 렌더링한 결과를 RSC Payload라는 형태로 만듭니다.
- 서버 컴포넌트가 렌더링한 결과
- 클라이언트 컴포넌트가 들어갈 위치
- 클라이언트 컴포넌트의 js 파일 참조
- 서버에서 클라이언트로 전달하는 Props
첫 페이지 로드 시
- 서버가 HTML을 생성해 브라우저에 전달
- 브라우저는 HTML 정적 화면을 미리 보여줌
- RSC Payload를 이용해 서버/클라이언트 컴포넌트 트리를 구성
- JS가 클라이언트 컴포넌트에 이벤트 연결(hydration)
서버에서 클라이언트로 데이터 전달
서버 컴포넌트에서 가져온 데이터는 props를 통해 클라이언트 컴포넌트로 전달할 수 있습니다.
클라이언트 컴포넌트 -> 서버 컴포넌트로 데이터 전달은 불가 server action이나 api를 통해 서버 로직을 호출하는식으로 해야함.
서버 컴포넌트와 클라이언트 컴포넌트 조합
클라이언트 컴포넌트의 children으로 서버 컴포넌트를 전달할 수 있습니다.
클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import 불가 (서버컴포넌트가 클라이언트 번들에 포함되기때문)
서드파티 컴포넌트
클라이언트 기능을 사용하는 외부 라이브러리가 "use client"를 선언하지 않았다면, 직접 클라이언트 컴포넌트로 감싸서 사용
Zustand
Zustand는 React 애플리케이션에서 전역 상태를 관리하기 위한 상태 관리 라이브러리입니다.
특징 및 제공하는 기능
- 사용법이 간단함
- Redux는 action, reducer, dispatch를 필수로 작성해야하지만, zustand는 그럴 필요가 없습니다.
- React Context는 provider로 컴포넌트를 감싸야하지만, zustand는 그럴 필요가 없습니다.
- store 내부에 상태와 상태를 변경하는 액션을 정의하고, set 함수를 통해 직접 업데이트 할수있습니다.
- Selector 기반 구독
- store 전체를 구독하지 않고 필요한 상태만 선택해서 구독할 수 있습니다.
- 상태와 액션을 한 곳에서 관리
- 상태뿐만 아니라 상태를 변경하는 함수도 store 내부에 함께 관리할 수 있습니다.
- React 외부에서도 사용 가능
- 비동기 로직 작성이 간단함
- 별도의 미들웨어 없이 async 함수를 액션으로 작성할 수 있습니다.
- ts 지원
- 상태와 액션에 타입을 지정할 수 있으며, selector와 액션에도 타입 추론이 적용됩니다.
- 다양한 미들웨어 제공
- persist
- localStorage, sessionStorage, AsyncStorage(앱 저장소) 등에 상태를 저장
- 새로고침 후에도 상태유지가능
- devtools
- Redux DevTools와 연동해 상태 변경 내역을 확인
- immer
- subscribeWithSelector
- 리액트 밖에서 특정 상태조각만 구독하고싶을때 사용
- redux
- redux 스타일의 reducer, dispatch 패턴 사용하고싶을때
- persist
- 작은 번들 사이즈
단점
- 자유로운 구조가 오히려 독이될수도있음
- 사람마다 store를 다르게 작성하거나
- store가 지나치게 커질 수 있음
- 전역 상태를 남용하기 쉬움
- 잘못된 사용으로인한 성능저하
- SSR과 persist 사용 시 주의해야함
- 사용자 간 상태가 섞이거나 hydration mismatch가 발생할 수 있음
- 서버와 클라이언트의 첫 렌더링 결과를 동일하게 만들어서 방지. hydration이후 실제 상태로 업데이트하는것
hydration mismatch : 서버에서 만든 html과 클라이언트가 처음 렌더링한 결과가 다를때 발생하는 오류
Tanstack Query
서버에서 가져운 데이터 (서버 상태)를 관리하는 라이브러리
특징 및 제공기능
- api 요청 상태 관리
- 캐싱과 중복 요청 제거
- stale 데이터 관리
- 백그라운드 refetch
- 페이지네이션과 무한 스크롤
- 낙관적 업데이트
- SSR hydration
staleTime과 gcTime의 차이
- staleTime은 데이터를 fresh 상태로 간주하는 시간입니다.
- gcTime은 사용중이지 않은 쿼리를 캐시에 얼마나 보관할지 결정하는 시간입니다. -> staleTime은 언제 데이터를 새로 불러올건지 -> gcTime은 안쓰는 데이터를 언제 삭제할것인지
캐시 생명주기
- queryKey를 기준으로 캐시를 관리합니다.
- 쿼리의 응답은 QueryClient 내부의 QueryCache에 저장됩니다.
- 같은 QueryClient를 사용하는 컴포넌트 두 개가 같은 queryKey를 사용하면 캐시 데이터를 공유합니다.
- 해당 쿼리를 사용하는 컴포넌트가 모두 사라지면 inactive 상태가 됩니다.
- inactive 상태가 된 뒤 gcTime이 지나면 캐시에서 제거됩니다.
useMutation
- useMutation은 생성,수정,삭제처럼 서버에 변경을 발생시키는 작업에 사용합니다.
- mutation 성공 후 목록을 갱신하기 위해서는 onSuccess나 OnSettled에서 관련 쿼리를 invalidateQueries를 사용해 무효화합니다.
- onMutate: mutate 요청 직전 실행
- onError: 요청 실패 시 실행
- onSuccess: 요청 성공 시 실행
- onSettled: 성공/실패 상관없이 마지막에 실행
낙관적 업데이트
- 서버 성공/실패 여부와 관계 없이 ui를 먼저 변경하는 방식
- 실패하면 롤백해야함
- 일반적으로 다음 순서로 진행됨
- onMutate -> 이전 데이터 저장 + UI 먼저 변경
- onError -> 실패하면 이전 데이터로 복구
- onSuccess -> 성공하면 데이터 반영
- onSettled -> 최종적으로 서버와 동기화
페이지네이션과 prefetch
- 페이지 번호를 queryKey에 포함 -> 페이지가 변경되면 새로운 쿼리로 인식되어 해당 페이지 데이터가 별도로 캐싱됨.
- 무한스크롤은 useInfiniteQuery를 사용. 다음 페이지를 getNextPageParam으로 반환, fetchNextPage를 호출해서 다음 데이터를 가져옴
Prefetch
- 사용자가 곧 접근할 데이터를 미리 요청해 캐시에 저장하는 방식
- 다음페이지나 특정 상세 페이지를 미리 요청해 페이지 전환 시 더 빠르게 데이터를 보여주는 방식
- request waterfall을 방지하는 목적으로 사용
request waterfall : api 요청이 순차적으로 이어지면서 전체 응답 시간이 길어지는 현상
select 옵션
- 캐시에 저장된 원본 데이터 중 컴포넌트에서 필요한 형태만 선택하는 옵션
- 쿼리 결과 전체 객체를 무분별하게 props로 전달하기보다 select로 필요한 데이터만 반환하여 불필요한 리렌더링을 줄일 수 있음 -> 필요없는 데이터까지 전달하면, 안쓰는 데이터가 바뀌어도 해당 컴포넌트를 리렌더링해야하기때문.
isPending / isLoading / isFetching 차이
| 상황 | isPending | isLoading | isFetching |
|---|---|---|---|
| 최초 요청 중 | true | true | true |
| 데이터 없이 enabled: false | true | false | false |
| 데이터 표시 중 | false | false | false |
| 기존 데이터 백그라운드 갱신 중 | false | false | true |
SSR에서의 Tanstack Query 사용
-
서버에서 queryClient 생성
-
필요한 쿼리 미리 요청
-
dehydrate로 캐시를 직렬화
-
클라이언트에서 HydrationBoundary로 복원
-
SSR에서는 queryClient를 전역 싱글톤으로 만들지말고 요청 단위로 분리해야함 -> 서버에서는 여러 사용자의 요청이 동시에 처리될 수 있기때문
-
QueryClientProvier는 클라이언트컴포넌트에 배치해야함. -> React Context와 브라우저 이벤트를 사용하는 클라이언트 전용 Provider이기때문. -> 서버컴포넌트에서는 필요한 데이터를 prefetch -> hydration 후에 실제 query hook을 사용하는 컴포넌트는 클라이언트 컴포넌트로 구성하는 방식이 일반적임
zod
zod는 타입스크립트 환경에서 사용하는 스키마 선언 및 런타임 데이터 검증 라이브러리입니다. 타입스크립트만 사용할때는 런타임 데이터 검증을 할 수 없기 때문에 zod를 사용합니다.
ts와 zod의 차이
- ts는 개발 단계에서 타입 오류를 검사하고, 실행 시에는 제거됩니다.
- zod는 실행 중에 실제 데이터를 검사합니다. -> 클라이언트 코드에서 Zod를 import하면 클라이언트 번들에 포함됩니다.
스키마
- zod 스키마는 데이터의 구조와 각 필드의 조건을 정의합니다.
parse와 safeParse의 차이
- parse는 검증에 실패하면 ZodError를 throw합니다.
- SafeParse는 에러를 throw하지 않고 성공/실패 객체를 반환합니다. -> 실패가 예상되는 일반적인 상황에서는 SafeParse를 쓰는게 좋습니다. -> 반면 환경변수처럼 값이 잘못된경우 즉시 중단해야하는 데이터에는 parse를 사용할 수 있습니다. 공식문서 - 에러핸들링
z.infer
- zod 스키마로부터 Typescript 타입을 자동으로 추출하는 기능입니다.
- z.infer만 사용한다고 런타임 검증도 자동으로 되는게 아닙니다. parse나 safeParse를 실행해야합니다.
optional, nullable, nullish의 차이
z.string().optional(): 값이 문자열 또는 undefined일 수 있음z.string().nullable(): 값이 문자열 또는 null일 수 있음z.string().nullish(): 값이 문자열, null, undefined 중 하나일 수 있음
defualt와 optional의 차이
- optional은 값이 없어도 허용하지만 실제 결과가 undefined일 수 있음
- default는 값이 없을 때 기본값을 넣어줌
z.string().default('light')
React Hook Form
React에서 폼 상태와 검증을 관리하기 위한 라이브러리입니다. 입력값, 에러, 제출 상태, touched 상태 등을 관리하며, 불필요한 리렌더링을 줄이는 데 초점을 둡니다.
RFH이 성능을 최적화 하는 법
React의 useState로 모든 input 값을 관리하면 입력할 때마다 컴포넌트가 리렌더링될 수 있습니다. -> 컴포넌트 리렌더링은 state가 변경될때도 일어나기 떄문
RHF는 기본적으로 uncontrolled input과 ref를 활용해 입력값을 DOM에서 관리하므로 리렌더링을 줄이고 폼 성능을 개선합니다.
제어컴포넌트 / 비제어컴포넌트
- 제어컴포넌트는 react에서 컴포넌트 값을 관리합니다.
- 비제어 컴포넌트는 dom이 직접 컴포넌트 값을 관리합니다.
-> RHF은 비제어컴포넌트를 통해 불필요한 렌더링을 방지합니다.