금요일

TypeScript 제네릭 타입 추론 완벽 정복

🔍 검색 키워드: TypeScript 제네릭 타입 추론, 제네릭 제약 조건, Generic extends, TypeScript 고급 타입, 타입 안전성


TypeScript 제네릭 타입 추론 완벽 정복


증상: 제네릭이 자동 추론되지 않는다

함수에 제네릭을 사용했는데 타입이 unknown이거나 명시적으로 지정해야 하나요?


이것이 제네릭 타입 추론의 복잡성입니다. 동작하지 않으면 수동 타입 지정이 필요하지만, 올바른 제약 조건을 설정하면 자동 추론이 가능합니다.


원인 분석


원인 1: 제약 조건 없는 제네릭

function processArray<T>(arr: T[]): T[] {

  return arr.sort(); // ❌ 에러: T에 sort가 있다는 보장 없음

}


원인 2: 여러 제네릭 간 관계 미정의

function transform<T, U>(input: T): U {

  return input as U; // ❌ 위험한 as 사용

}


해결 방법


✅ 방법 1: 제약 조건으로 구조 정의 (extends)

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {

  return obj[key]; // ✓ key는 T의 키만 가능

}


✅ 방법 2: infer로 중첩 타입 추출

type GetArrayElement<T> = T extends Array<infer U> ? U : never;


type NumArray = GetArrayElement<number[]>; // number ✓


✅ 방법 3: 조건부 타입으로 분기

type IsString<T> = T extends string ? true : false;


type A = IsString<'hello'>; // true ✓

type B = IsString<number>; // false ✓


결론

- 제약 조건(extends)은 필수 — 타입 안전성과 추론 향상

- infer로 타입 추출 — 제네릭 값을 유연하게 활용

- 조건부 타입으로 분기 — 복잡한 타입 관계 표현

- 오버로드로 정확하게 — 여러 입출력 조합 처리


제네릭을 마스터하면 TypeScript의 진정한 강력함을 경험할 수 있습니다.

목요일

React useEffect 무한루프 문제 완벽 해결 가이드

🔍 검색 키워드: React useEffect 무한루프, useEffect 의존성 배열, React Hook 성능 최적화, useEffect 디버깅

React useEffect 무한루프 문제 완벽 해결 가이드

증상: useEffect가 계속 실행된다

개발 중 콘솔을 보니 로그가 무한히 출력되거나 API 요청이 계속 날아가고 있나요?

useEffect: 무한 실행 상태
  console.log("Effect 실행"); // 계속 반복

이것이 useEffect 무한루프입니다. 성능 저하, 메모리 누수, API 서버 부하까지 초래하는 심각한 문제입니다.


원인 분석

원인 1: 의존성 배열 누락

useEffect(() => {
    console.log("매 렌더링마다 실행");
      fetchData();
      }, ); // 의존성 배열 없음 = 모든 렌더링마다 실행

해결 방법

✅ 방법 1: 의존성 배열 명시 (필수!)

올바른 코드:

useEffect(() => {
    fetchData();
    }, []); // 마운트 시 1회만 실행

결론

  • 의존성 배열은 필수 — 생략하면 무한루프
  • 의존성 값은 안정화 — 객체/배열은 useMemo, useCallback으로

Docker 멀티스테이지 빌드 캐시 최적화하기

🔍 검색 키워드: Docker 멀티스테이지 빌드, Dockerfile 캐시 성능, Docker 레이어 캐싱, 빌드 시간 단축

Docker 멀티스테이지 빌드 캐시 최적화하기

증상

Dockerfile로 이미지를 빌드할 때마다 다음과 같은 문제가 발생한다:

$ docker build -t myapp:latest .
  Step 3/10 : RUN npm install    # 매번 30초 이상 소요
  Step 4/10 : RUN npm run build  # 매번 60초 이상 소요
  ...
  real    2m 15s

코드는 조금만 바뀌었는데도 빌드 시간이 계속 길어진다. CI/CD 파이프라인에서 빌드만 몇 분씩 걸려서 배포 속도가 느리다. 특히 npm, pip 같은 의존성 설치 단계가 매번 전부 다시 실행된다.

원인

Docker는 각 RUN, COPY, ADD 단계를 하나의 레이어로 취급하고, 파일이 변경되면 그 이후의 모든 레이어를 다시 빌드한다. 예를 들어:

FROM node:18
  COPY . /app              # 레이어1: 모든 파일 복사
  WORKDIR /app
  RUN npm install          # 레이어2: 의존성 설치 (레이어1 변경되면 캐시 무효)
  RUN npm run build        # 레이어3: 빌드 (레이어2 변경되면 캐시 무효)

코드를 한 줄만 바꿔서 COPY . /app을 실행하면 npm install, npm run build가 모두 다시 실행된다. 즉, 캐시를 제대로 활용하지 못한다.

멀티스테이지 빌드를 사용하면서도 각 스테이지 간 의존성이 꼬여 있으면 캐시 효율이 떨어진다.

해결방법

1. 의존성 파일을 먼저 복사 (핵심!)

FROM node:18 AS builder
  
  WORKDIR /app
  
  # package.json, package-lock.json만 먼저 복사
  COPY package*.json ./
  
  # 의존성 설치 (코드 변경 시 캐시 유지)
  RUN npm ci
  
  # 이제 코드 복사
  COPY . .
  
  # 빌드
  RUN npm run build
  
  # 런타임 스테이지
  FROM node:18-alpine
  
  WORKDIR /app
  
  # 의존성만 복사 (빌드 아티팩트 불필요)
  COPY --from=builder /app/node_modules ./node_modules
  COPY --from=builder /app/dist ./dist
  COPY package*.json ./
  
  EXPOSE 3000
  CMD ["node", "dist/index.js"]

캐시 효과:

  • package.json 불변 → npm install 캐시 유지
  • 코드만 변경 → COPY . . 부터만 재실행
  • 빌드 시간 30초 → 5초

2. Docker Buildkit으로 고급 캐싱 활용

// syntax=docker/dockerfile:1.4
  
  FROM node:18 AS builder
  
  WORKDIR /app
  
  COPY package*.json ./
  
  // --mount=type=cache로 npm cache 디렉토리 보존
  RUN --mount=type=cache,target=/root/.npm \
      npm ci --prefer-offline
      
      COPY . .
      RUN npm run build
      
      FROM node:18-alpine
      
      WORKDIR /app
      
      COPY --from=builder /app/node_modules ./node_modules
      COPY --from=builder /app/dist ./dist
      COPY package*.json ./
      
      EXPOSE 3000
      CMD ["node", "dist/index.js"]

빌드 명령:

DOCKER_BUILDKIT=1 docker build -t myapp:latest .

효과:

  • npm cache가 빌드 간에 유지되어 더 빠름
  • 오프라인 설치 최적화

3. 불필요한 파일 제외 (.dockerignore)

.git
  .gitignore
  node_modules
  npm-debug.log
  .env
  .env.local
  dist
  build
  coverage
  .DS_Store
  README.md

효과:

  • COPY . . 시 변경되지 않은 파일이 많으면 해시 계산이 더 오래 걸림
  • .dockerignore로 제외하면 캐시 재계산 범위 축소

4. 여러 스테이지에서 캐시 공유

// syntax=docker/dockerfile:1.4
  
  FROM node:18 AS dependencies
  
  WORKDIR /app
  COPY package*.json ./
  
  RUN --mount=type=cache,target=/root/.npm \
      npm ci
      
      // 빌드 스테이지1
      FROM dependencies AS builder1
      
      COPY . .
      RUN npm run build
      
      // 빌드 스테이지2 (테스트)
      FROM dependencies AS tester
      
      COPY . .
      RUN npm test
      
      // 최종 스테이지
      FROM node:18-alpine
      
      WORKDIR /app
      
      COPY --from=builder1 /app/dist ./dist
      COPY --from=dependencies /app/node_modules ./node_modules
      COPY package*.json ./
      
      EXPOSE 3000
      CMD ["node", "dist/index.js"]

5. 번들 사이즈 최소화 (캐시 영향 없지만 이미지 크기 감소)

FROM node:18 AS builder
  
  WORKDIR /app
  COPY package*.json ./
  RUN npm ci
  
  COPY . .
  RUN npm run build
  
  // 크기 최소화: dependencies만 복사, devDependencies 제외
  FROM node:18-alpine
  
  WORKDIR /app
  
  COPY --from=builder /app/dist ./dist
  
  // 본번 의존성만 설치 (devDependencies 없음)
  COPY package*.json ./
  RUN npm ci --omit=dev
  
  EXPOSE 3000
  CMD ["node", "dist/index.js"]

정리표

문제 원인 해결법
npm install 매번 재실행 COPY . . 후 RUN npm install package.json만 먼저 복사
캐시가 가끔만 작동 불필요 파일 포함 시 해시 변경 .dockerignore 작성
빌드 시간 여전히 길다 npm cache 미보존 Buildkit + --mount=cache 활용
이미지 크기 커짐 devDependencies 포함 npm ci --omit=dev
멀티스테이지 간 캐시 미공유 각 스테이지가 독립적 dependencies AS 공용 스테이지 생성

TIP: docker build --progress=plain으로 상세 로그 확인, docker image history myapp:latest로 각 레이어 크기 확인 가능. 캐시 강제 무효화는 docker build --no-cache 또는 COPY . . --chown=node:node 같은 타임스탐프 변경 명령으로.