본문으로 이동

Use Better Terms

AI가 작성한 문서의 표현을 더 자연스럽고 이해하기 쉽게 개선할 수 있습니다.

수정한 부분과 이유

보고서 도입

  1. 변경 1

    수정 전

    ## Props Drilling 최소화 · Hooks 기반 DI / IoC / OCP · 실행 환경별 대응 - 작성 기준일: 2026-09-30 - 조사 방식: 공식 문서·라이브러리 공식 문서·실제 소스 코드(PR/테스트 코드)를 우선 참조했고, 블로그·커뮤니티 글은 보조로만 사용했습니다. - 이 문서는 이전 대화 없이 단독으로 읽을 수 있도록 작성했습니다. 용어 정의와 배경도 포함합니다. - 표기 규칙 - **[공식]**: React / Next.js / React Router / TanStack / Zustand / Radix 등 1차 문서에서 확인한 내용입니다. - **[코드]**: 실제 저장소의 소스·테스트 코드에서 확인한 내용입니다. - **[커뮤니티]**: 블로그·개인 저장소 등 비공식 자료입니다. 참고용이며 근거로 삼기에는 약합니다. - **[판단]**: 조사 자료를 바탕으로 한 작성자의 설계 판단입니다. 공식 권장이 아닙니다.

    수정 후

    **결론:** React에서는 먼저 props와 `children`으로 값을 전달하고, 여러 단계 아래의 컴포넌트가 같은 값을 읽어야 할 때 Context를 검토한다. 의존성 주입은 props, Context 또는 함수 인자로 구성할 수 있다. 서버 렌더링 환경에서는 요청 사이에 사용자별 상태를 공유하지 않아야 한다. 작성 기준일: 2026-09-30 근거 표기: [공식]은 공식 문서, [코드]는 공개된 소스나 테스트, [커뮤니티]는 비공식 자료, [판단]은 자료를 토대로 제안한 설계를 뜻한다.

    수정 이유

    표현 수정: 작성 과정 설명과 중복 부제를 덜고 보고서의 결론을 먼저 제시했습니다.

0. 요약

  1. 변경 1

    수정 전

    ## 0. 한눈에 보는 결론 1. **Props Drilling은 "Context 도입"이 첫 번째 해법이 아닙니다.** React 공식 문서의 순서는 (1) props로 명시적으로 전달 → (2) 컴포넌트 추출 후 JSX를 `children`으로 전달 → (3) 그래도 깊으면 Context입니다. 데이터를 쓰지 않는 중간 컴포넌트를 거치는 drilling은 컴포넌트 추출을 놓쳤다는 신호인 경우가 많다고 설명합니다. 2. **훅으로 DI/IoC를 구현하는 표준 형태는 "인터페이스(타입) + Context + `useXxx` 훅"입니다.** 컴포넌트는 구현체를 import하지 않고 훅이 돌려주는 인터페이스만 사용합니다. 구현체 결정은 상위 Provider가 담당합니다(제어의 역전). TanStack Query의 `QueryClientProvider` / `useQueryClient`가 이 패턴의 대표적인 실제 사례입니다. 3. **OCP(개방-폐쇄)는 "합성"으로 구현합니다.** 상속이 아니라 `children`/슬롯 props, `asChild`(Radix), Provider 교체, 렌더러 레지스트리로 기존 코드를 수정하지 않고 동작을 확장합니다. 4. **실행 환경이 바뀌면 제약이 달라집니다.** 특히 Next.js App Router에서는 Server Component에서 Context를 만들거나 읽을 수 없으므로, Provider는 `'use client'` 파일에 두고 서버 쪽 의존성은 다른 방식(함수 인자, `React.cache`, 프레임워크 제공 컨텍스트)으로 다뤄야 합니다. 5. **DI 컨테이너 라이브러리(Inversify 기반 등)는 기본값이 아닙니다.** 조사한 라이브러리는 모두 커뮤니티 자료이며 일부는 archive 상태입니다. 기본 전략은 Context + 훅이고, 컨테이너는 교체·테스트 요구가 실제로 커진 뒤에 검토하는 것이 안전합니다([판단]). 6. **React Compiler를 쓰면 Context 값 안정화용 `useMemo`/`useCallback`을 직접 관리할 부담이 줄어듭니다.** 다만 도입 여부와 범위는 프로젝트별로 검증해야 합니다.

    수정 후

    ## 0. 요약 1. **Props Drilling에는 props 전달과 컴포넌트 추출부터 검토합니다.** React는 Context를 쓰기 전에 props나 JSX를 `children`으로 전달하는 방법을 안내합니다. 중간 컴포넌트가 값을 사용하지 않고 전달하기만 한다면 추출할 수 있는지 살펴봅니다. [공식] 2. **Context와 훅은 의존성을 전달하는 방법 중 하나입니다.** 상위 Provider가 구현체를 제공하고 하위 컴포넌트가 훅으로 읽게 할 수 있습니다. TanStack Query의 `QueryClientProvider`와 `useQueryClient`가 Context를 사용하는 사례입니다. [공식] 3. **합성으로 컴포넌트를 확장할 수 있습니다.** `children`, 슬롯 props, Radix의 `asChild`는 기존 컴포넌트의 코드를 바꾸지 않고 내용이나 렌더링 요소를 교체하는 데 쓰입니다. Provider나 렌더러 맵을 바꿔야 하는 경우에는 수정 지점을 따로 확인해야 합니다. [공식, 판단] 4. **실행 환경에 따라 사용할 수 있는 수단이 달라집니다.** Next.js App Router의 Server Component는 Context를 생성하거나 읽을 수 없습니다. React 19.3에서는 클라이언트 모듈이 내보낸 Context를 Server Component가 직접 렌더링할 수 있습니다. 사용하는 Next.js 버전에서 가능한지는 확인해야 합니다. [공식] 5. **DI 컨테이너는 필요가 확인될 때 검토합니다.** props, Context, 함수 인자로 해결할 수 있는지 먼저 확인합니다. 3.5절의 라이브러리는 모두 커뮤니티 구현이며 일부 저장소는 보관 처리되었습니다. [판단] 6. **React Compiler는 자동 메모이제이션을 제공합니다.** 새 코드에서 `useMemo`와 `useCallback`이 필요한 경우는 줄어들 수 있지만, 세밀한 제어가 필요하면 계속 사용할 수 있습니다. [공식]

    수정 이유

    기술 설명 보정: DI와 OCP의 한 가지 구성을 표준이나 무수정 확장으로 단정하지 않고 적용 조건을 밝혔습니다.

1. 용어 정의

  1. 변경 1

    수정 전

    | Props Drilling | 데이터를 쓰지 않는 중간 컴포넌트를 여러 층 거쳐 props를 전달하는 상황 | | DI (의존성 주입) | 컴포넌트/훅이 필요한 협력 객체(API 클라이언트, 저장소, 분석 도구 등)를 직접 생성·import하지 않고 외부에서 받는 것 | | IoC (제어의 역전) | "어떤 구현을 쓸지"에 대한 결정권이 사용하는 쪽이 아니라 상위(Provider, 앱 루트)에 있는 구조 | | OCP (개방-폐쇄 원칙) | 확장에는 열려 있고 수정에는 닫혀 있는 구조. 새 동작을 추가할 때 기존 컴포넌트 코드를 고치지 않는 것을 목표로 함 | | RSC | React Server Components. 서버에서만 실행되는 컴포넌트. Hook·Context를 쓸 수 없음 |

    수정 후

    | Props Drilling | 값을 쓰지 않는 중간 컴포넌트를 여러 층 거쳐 props를 전달하는 상황 | | DI (의존성 주입) | 컴포넌트나 훅이 필요한 API 클라이언트나 저장소 구현체를 직접 생성하지 않고 외부에서 받는 방식 | | IoC (제어의 역전) | "어떤 구현을 쓸지"에 대한 결정권이 사용하는 쪽이 아니라 상위(Provider, 앱 루트)에 있는 구조 | | OCP (개방-폐쇄 원칙) | 새 동작을 추가할 때 기존 구현에서 수정해야 하는 부분을 줄이는 설계 원칙 | | RSC | React Server Components. 서버에서 실행되며 상태와 effect를 사용하는 클라이언트 Hook을 호출할 수 없음. Context 생성과 읽기도 할 수 없지만, React 19.3에서는 클라이언트 모듈의 Context를 렌더링할 수 있음 |

    수정 이유

    기술 설명 보정: DI, OCP와 Server Component의 정의에 실제로 가능한 동작을 반영했습니다.

2. 단계적 접근 전략

  1. 변경 1

    수정 전

    아래 순서로 올라가고, 위 단계로 충분하면 멈춥니다. 각 단계는 앞 단계보다 결합이 "보이지 않게" 되므로 비용이 큽니다. ### 2.1 1단계 — props로 명시 전달 [공식] - 공식 문서는 props를 먼저 쓰라고 권합니다. 데이터 흐름이 명시적이라 어떤 컴포넌트가 어떤 데이터를 쓰는지 드러나고, 유지보수자에게 유리하기 때문입니다.

    수정 후

    React 문서가 props, 컴포넌트 추출, Context를 검토하는 순서를 설명합니다. 외부 스토어와 DI 컨테이너를 그 뒤에 놓은 것은 이 보고서의 설계 제안입니다. 다른 수단이 필요한 문제가 확인되었을 때 추가 도구를 검토합니다. ### 2.1 1단계 — props로 명시 전달 [공식] - React는 props를 먼저 쓰라고 권합니다. 어떤 컴포넌트가 어떤 값을 받는지 드러나기 때문입니다.

    수정 이유

    표현 수정: 추상적인 단계 설명 대신 React 문서의 권고와 이 보고서의 제안을 구별했습니다.

  2. 변경 2

    수정 전

    - 중간 컴포넌트가 데이터를 쓰지 않고 넘기기만 한다면, JSX 자체를 `children`이나 `left`, `right` 같은 props로 내려 보내 중간 층을 없앱니다. - React 요소는 객체이므로 props로 자유롭게 넘길 수 있습니다(슬롯 개념). 이전 React 문서(Composition vs Inheritance)는 이를 Containment(포함)와 Specialization(특수화)으로 설명하며, 상속 대신 합성을 권합니다. 해당 문서는 더 이상 갱신되지 않는 구 문서입니다.

    수정 후

    - 중간 컴포넌트가 값을 쓰지 않고 전달하기만 한다면, JSX 자체를 `children`이나 `left`, `right` 같은 props로 넘겨 불필요한 전달 단계를 없앱니다. - React 요소는 props로 전달할 수 있습니다. 이전 React 문서(Composition vs Inheritance)는 이를 포함과 특수화의 예로 설명하며 상속 대신 합성을 권합니다. 이 문서는 더 이상 갱신되지 않습니다.

    수정 이유

    표현 수정: 중간 컴포넌트가 전달하는 값과 불필요한 전달 단계를 명시했습니다.

  3. 변경 3

    수정 전

    - Context는 부모가 아래 트리 전체에 데이터를 제공하게 해 줍니다. 읽는 쪽은 `useContext`(또는 `use`)를 씁니다.

    수정 후

    - Context를 제공하는 컴포넌트는 하위 트리에 값을 전달할 수 있습니다. 필요한 컴포넌트가 `useContext` 또는 `use`로 그 값을 읽습니다.

    수정 이유

    표현 수정: Context 값을 제공하는 컴포넌트와 읽는 컴포넌트를 구분했습니다.

  4. 변경 4

    수정 전

    ### 2.4 4단계 — DI 컨테이너/외부 스토어 (필요할 때만) [판단] - 구현체가 여러 개이고 교체·테스트 요구가 크거나, 의존성 그래프가 복잡할 때만 고려합니다.

    수정 후

    ### 2.4 4단계 — DI 컨테이너나 외부 스토어 (필요할 때만) [판단] - 여러 구현체를 교체해야 하거나 의존성 관리가 실제로 복잡해졌다면 DI 컨테이너를 검토합니다. 빈번히 바뀌는 공유 상태 때문에 Context를 읽는 컴포넌트가 자주 다시 렌더링된다면 외부 스토어를 검토합니다.

    수정 이유

    기술 설명 보정: DI 컨테이너와 외부 스토어를 검토할 조건이 서로 다름을 밝혔습니다.

3. DI와 IoC

  1. 변경 1

    수정 전

    > 아래 코드는 본 보고서 작성자가 공식 API 사용법을 바탕으로 작성한 예시입니다. 그대로 복사해 쓰기 전에 프로젝트의 React/TypeScript 버전에서 타입 검사를 통과하는지 확인해 주세요. 예시는 React 19 문법(`<Ctx value={...}>`)을 사용합니다.

    수정 후

    아래 코드는 React 19의 `<Ctx value={...}>` 문법을 사용한 구성 예시입니다. 여러 파일로 나눈 예시이므로 import와 실행 환경에 따른 설정을 함께 확인해야 합니다.

    수정 이유

    표현 수정: 예시 작성 과정을 설명하는 문장을 덜고 코드의 문법과 적용 조건을 설명했습니다.

  2. 변경 2

    수정 전

    throw new Error(`${name}Provider 안에서 사용해야 합니다`);

    수정 후

    throw new Error(`${name} Provider 안에서 사용해야 합니다`);

    수정 이유

    예시 교정: Provider 누락 오류 메시지에서 이름과 Provider 사이의 공백을 바로잡았습니다.

  3. 변경 3

    수정 전

    // domain/post-repository.ts (인터페이스: 도메인이 소유)

    수정 후

    // domain/post-repository.ts (도메인 코드가 사용하는 인터페이스)

    수정 이유

    표현 수정: 인터페이스를 도메인이 관리한다고 단정한 주석 대신 이를 사용하는 코드를 주석에 명시했습니다.

  4. 변경 4

    수정 전

    - **IoC 지점**: 구현체(`createHttpPostRepository`)를 고르는 곳은 앱 루트 Provider뿐입니다. 컴포넌트와 훅은 `PostRepository` 인터페이스만 압니다. - 이 절차(어댑터 인터페이스 정의 → Context 생성 → 루트 Provider에서 구현 주입 → 훅으로 사용)는 커뮤니티 DI 가이드에서도 같은 순서로 소개됩니다[커뮤니티]. 공식 문서가 "DI"라는 이름으로 규정한 패턴은 아니며, Context의 정상적인 사용법을 DI 목적에 적용한 것입니다([판단]).

    수정 후

    - **IoC 지점:** 앱 루트 Provider가 `createHttpPostRepository`로 구현체를 만듭니다. `usePosts`는 `PostRepository` 타입으로 정의된 메서드를 호출합니다. - 필요한 메서드의 타입을 정하고, Provider가 구현체를 전달하며, 훅에서 읽는 구성입니다. React가 이를 표준 DI 패턴으로 규정한 것은 아닙니다. [판단]

    수정 이유

    기술 설명 보정: Context 기반 의존성 전달을 React가 정한 표준 DI 형식으로 소개하지 않도록 수정했습니다.

  5. 변경 5

    수정 전

    // 렌더마다 새 객체가 만들어지지 않도록 lazy 초기화

    수정 후

    // Provider 인스턴스가 유지되는 동안 같은 의존성 객체를 사용

    수정 이유

    표현 수정: 의존성 객체가 유지되는 기간을 Provider 인스턴스에 맞춰 설명했습니다.

  6. 변경 6

    수정 전

    - `useState(() => create...())`로 인스턴스를 한 번만 만드는 방식은 Zustand 공식 Next.js 가이드가 스토어 Provider에서 사용하는 형태와 같습니다[공식]. - 주의: SSR 환경에서는 Client Component도 서버에서 한 번 렌더링됩니다. 의존성 생성 코드가 `window` 같은 브라우저 전용 API를 직접 쓰면 서버에서 실패합니다. 브라우저 전용 코드는 `client-only` 표시나 `useEffect` 내부로 격리합니다([판단], Next.js 문서의 `client-only` 안내 참조).

    수정 후

    - `useState`의 초기화 함수로 Provider 인스턴스마다 의존성 객체를 만드는 구성은 Zustand의 Next.js 스토어 Provider 예시와 유사합니다. [공식] - 초기 요청에서 Client Component도 서버에서 렌더링될 수 있습니다. 생성 함수가 `window` 같은 브라우저 API에 접근한다면 서버 렌더링 중 오류가 납니다. 브라우저에서만 실행할 모듈은 `client-only`로 표시하고, 실행 시점도 별도로 조정해야 합니다. [공식, 판단]

    수정 이유

    기술 설명 보정: 서버 렌더링 중 브라우저 API에 접근하면 실패하는 조건을 구체화했습니다.

  7. 변경 7

    수정 전

    ```tsx import { renderHook, waitFor } from '@testing-library/react';

    수정 후

    React Testing Library의 `wrapper` 옵션을 사용하면 테스트에서 별도의 Provider를 전달할 수 있습니다. `usePosts`를 검사한다면 `PostRepository`의 대체 구현을 전달하고 반환된 게시물 목록을 확인할 수 있습니다. TanStack Query 캐시가 다른 테스트에 영향을 주지 않도록 `QueryClient`는 테스트마다 따로 만듭니다. [코드, 판단] ```tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook, waitFor } from '@testing-library/react'; import type { ReactNode } from 'react';

    수정 이유

    예시 교정: 테스트에서 Provider와 QueryClient를 어떻게 마련하는지 설명하고 빠진 import를 채웠습니다.

  8. 변경 8

    수정 전

    const fakeDeps = { postRepository: { list: async () => [{ id: '1', title: 'x' }] } }; const wrapper = ({ children }: { children: React.ReactNode }) => ( <QueryClientProvider client={new QueryClient()}> <DepsContext value={fakeDeps}>{children}</DepsContext> </QueryClientProvider> ); test('posts를 가져온다', async () => { const { result } = renderHook(() => usePosts(), { wrapper }); await waitFor(() => expect(result.current.data).toHaveLength(1)); }); ``` - `renderHook`이 `wrapper` 옵션으로 Context Provider를 감싸는 것은 React Testing Library의 실제 테스트 코드에서 확인했습니다[코드]. - 테스트마다 새 `QueryClient`를 만드는 이유는 캐시가 테스트 사이에 공유되지 않게 하기 위해서입니다([판단]).

    수정 후

    test('게시물 목록을 읽는다', async () => { const client = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); const deps = { postRepository: { list: async () => [{ id: '1', title: '예시' }] }, }; const wrapper = ({ children }: { children: ReactNode }) => ( <QueryClientProvider client={client}> <DepsContext value={deps}>{children}</DepsContext> </QueryClientProvider> ); const { result } = renderHook(() => usePosts(), { wrapper }); await waitFor(() => expect(result.current.data).toEqual([{ id: '1', title: '예시' }])); }); ```

    수정 이유

    예시 교정: 테스트마다 QueryClient를 만들고 게시물의 실제 내용을 확인하도록 바꿨습니다.

  9. 변경 9

    수정 전

    | react-context-di | Context 위에 타입 안전한 컨테이너를 얹고, 테스트에서 컨테이너를 통째로 교체 | 작고 단순함. 채택 전 유지보수 상태 확인 필요 | | react-facade | 훅 구현을 Context로 주입하는 실험적 라이브러리. 스스로 "experimental"이라 밝힘 | 프로덕션 근거로 약함 | | react-injection (Inversify 기반) | HOC + `useInjection` | 저장소가 archive 상태. 신규 도입 비추천 | | react-ioc | 계층형 DI, 데코레이터 사용, React 16 Context 기반 | 데코레이터/클래스 중심이라 현대 훅 스타일과 거리가 있음 |

    수정 후

    | react-context-di | Context 위에 타입을 가진 컨테이너를 제공하고 테스트에서 교체할 수 있음 | 현재 유지보수 상태와 React 19 호환성 미확인 | | react-facade | 훅 구현을 Context로 전달하는 실험적 라이브러리 | 실제 앱에 적용하기 전 안정성과 호환성 확인 필요 | | react-injection (Inversify 기반) | HOC와 `useInjection` 제공 | 저장소가 보관 처리된 상태 | | react-ioc | 계층형 DI와 데코레이터 사용 | React 19 호환성 미확인 |

    수정 이유

    기술 설명 보정: 커뮤니티 라이브러리의 유지보수와 React 19 호환성을 확인하지 않은 채 평가하지 않도록 수정했습니다.

4. OCP

  1. 변경 1

    수정 전

    ### 4.1 슬롯 / children — "구조는 고정, 내용은 외부에서" [공식]

    수정 후

    ### 4.1 슬롯과 children: 컨테이너를 유지하고 내용을 교체 [공식]

    수정 이유

    표현 수정: 슬롯이 유지하는 부분과 바꾸는 부분을 제목에서 구별했습니다.

  2. 변경 2

    수정 전

    ### 4.2 asChild — "동작은 고정, 렌더링은 교체" [공식] - Radix는 모든 DOM 렌더링 파트가 `asChild` prop을 받습니다. `true`이면 기본 DOM 요소를 렌더링하지 않고, 자식 요소를 복제해 필요한 props와 동작을 전달합니다. - 디자인 시스템의 자체 컴포넌트(`MyButton`)에 동작(접근성, 이벤트)만 입히는 데 씁니다. - 제약: 커스텀 컴포넌트는 받은 props를 반드시 DOM으로 펼쳐서(spread) 전달하고 ref도 전달해야 합니다. 요소 타입을 바꾸면 접근성 책임은 사용자에게 있습니다(예: 툴팁 트리거는 포커스 가능해야 함).

    수정 후

    ### 4.2 asChild: 동작을 유지하고 렌더링 요소를 교체 [공식] - Radix의 `asChild`를 제공하는 컴포넌트에서는 이를 `true`로 지정하면 기본 DOM 요소 대신 자식 요소에 필요한 props와 동작을 전달합니다. - 디자인 시스템의 자체 컴포넌트(`MyButton`)에 동작(접근성, 이벤트)만 입히는 데 씁니다. - 제약: 커스텀 컴포넌트는 받은 props와 ref를 실제 DOM 요소에 전달해야 합니다. 요소 타입을 바꾼다면 툴팁 트리거의 포커스 가능 여부처럼 접근성 조건도 확인해야 합니다.

    수정 이유

    기술 설명 보정: Radix의 모든 구성 요소가 아닌 asChild를 제공하는 구성 요소에만 설명을 적용했습니다.

  3. 변경 3

    수정 전

    - 3장의 패턴 B/C가 여기에 해당합니다. 새 구현체(예: `createGraphqlPostRepository`)를 추가해도 `usePosts`와 컴포넌트는 수정하지 않습니다. Provider의 조립 코드만 바뀝니다. - Context는 트리 일부에서 다른 Provider로 덮어쓸 수 있습니다. React 문서는 하위 트리의 값을 다른 Provider로 감싸 재정의하는 방식을 설명합니다. 페이지·기능 단위로 구현을 바꾸는 데 활용할 수 있습니다. ### 4.4 렌더러 레지스트리 — "새 타입 추가 시 기존 분기문 수정 없음" [판단] ```tsx // features/feed/feed.tsx import type { ComponentType } from 'react'; export interface FeedItem { id: string; type: string } export type Renderers = Record<string, ComponentType<{ item: any }>>; export function Feed({ items, renderers }: { items: FeedItem[]; renderers: Renderers }) { return items.map((item) => { const Renderer = renderers[item.type]; return Renderer ? <Renderer key={item.id} item={item} /> : null; });

    수정 후

    - 3장의 패턴 B와 C에서는 다른 구현체를 Provider에 전달해도 `usePosts`를 수정할 필요가 없습니다. 대신 Provider의 조립 코드는 수정해야 합니다. - 하위 트리를 다른 Provider로 감싸면 그 트리에서 읽는 Context 값을 바꿀 수 있습니다. React 문서가 설명하는 이 동작을 페이지나 화면 일부에 적용할 수 있습니다. ### 4.4 렌더러 등록표: 유형별 렌더링을 외부에서 지정 [판단] 목록 항목의 유형을 렌더러에 연결하는 등록표를 컴포넌트에 전달할 수 있습니다. 새 유형을 추가할 때 `Feed` 내부의 분기문을 바꾸지 않아도 되지만, 등록표를 만드는 코드는 수정해야 합니다. 등록되지 않은 유형을 어떻게 표시할지와 유형별 렌더러의 props를 어떻게 검사할지는 별도 설계가 필요합니다. 이 방법을 React의 공식 OCP 패턴으로 제시할 근거는 확인되지 않았습니다. ```tsx import type { ReactNode } from 'react'; interface FeedItem { id: string; type: string; } type Renderers = ReadonlyMap<string, (item: FeedItem) => ReactNode>; export function Feed({ items, renderers }: { items: FeedItem[]; renderers: Renderers; }) { return items.map((item) => ( <section key={item.id}>{renderers.get(item.type)?.(item) ?? null}</section> ));

    수정 이유

    기술 설명 및 예시 교정: Provider 교체에도 조립 코드는 수정됨을 밝히고 렌더러 예시의 any를 없앴습니다.

  4. 변경 4

    수정 전

    - 새 타입(예: 광고 카드)은 `renderers` 맵에 항목만 추가하면 됩니다. `Feed` 안의 `switch`를 고칠 필요가 없습니다. - 이 패턴은 조사한 공식 문서에 이름이 붙은 형태로는 없었고, 위의 합성 원칙을 적용한 설계안입니다. `any` 사용은 예시 단순화를 위한 것이며, 실제로는 판별 유니온이나 제네릭으로 타입을 좁히는 것이 좋습니다. --- ## 5. 실행 환경별 대응 React 공식 문서는 새 프로젝트를 시작할 때 **프레임워크를 권장**합니다. 권장 목록은 Next.js(App Router), React Router v7, Expo(네이티브)입니다. 프레임워크로 맞지 않는 제약이 있거나 직접 만들고 싶다면 Vite, Parcel, Rsbuild 같은 빌드 도구로 처음부터 구성하는 방법도 공식 문서에 있습니다[공식]. 문서는 권장 프레임워크가 모두 CSR, SPA, SSG를 지원하고, 라우트 단위로 서버 렌더링을 나중에 켤 수 있다고 설명합니다.

    수정 후

    --- ## 5. 실행 환경에 따른 구성 React는 새 프로젝트를 시작할 때 프레임워크를 권장하며 Next.js App Router, React Router v7과 Expo를 예로 듭니다. 직접 구성하려면 Vite, Parcel 또는 Rsbuild 같은 빌드 도구로 시작할 수도 있습니다. 사용할 렌더링 방식은 선택한 프레임워크의 설정과 배포 환경을 확인해 정해야 합니다. [공식]

    수정 이유

    표현 수정: 렌더러 등록표에서 수정해야 하는 위치와 실행 환경별 설명을 구체화했습니다.

5. 실행 환경

  1. 변경 1

    수정 전

    | 환경 | Context / Hook | DI 주입 위치 | 핵심 주의점 | | --- | --- | --- | --- | | **A. 클라이언트 전용 SPA** (Vite 등 빌드 도구, 프레임워크의 SPA 모드) | 제약 없음 | 앱 루트의 Provider | 3장 패턴을 그대로 적용. 서버 관련 제약 없음 | | **B. Next.js App Router** (RSC 기본) | Client Component에서만 가능 | `'use client'` Provider(UI), 서버 코드는 함수 인자·`React.cache` | Server Component는 Context 불가, props 직렬화 필요, Provider는 트리 깊게 | | **C. React Router v7 프레임워크/데이터 모드** | 컴포넌트는 일반 Context, loader/action은 별도 컨텍스트 | 컴포넌트: Provider / loader: `RouterContextProvider`·middleware | 컴포넌트용 Context와 라우터 컨텍스트가 이름이 같지만 다른 것 | | **D. Expo/React Native** | 클라이언트 React와 동일한 규칙 | 앱 루트 Provider | 이번 조사에서 공식 자료 미확인 (추가 과제) |

    수정 후

    | 환경 | Context 사용 | 의존성을 제공하는 지점 | 주의할 점 | | --- | --- | --- | --- | | **A. 클라이언트 전용 SPA** | 컴포넌트에서 사용 가능 | 앱 루트의 Provider | 서버 요청 사이의 상태 공유 문제는 없음 | | **B. Next.js App Router** | Client Component에서 읽음. React 19.3의 Server Component는 클라이언트 모듈의 Context를 렌더링 가능 | 클라이언트 Provider 또는 서버 함수의 인자 | 클라이언트에 전달할 props는 직렬화 조건을 충족해야 함 | | **C. React Router v7 프레임워크 또는 데이터 모드** | 컴포넌트에는 React Context, loader와 action에는 라우터 context 사용 | 컴포넌트 Provider 또는 라우터 context | 두 `createContext`는 서로 다른 API | | **D. Expo/React Native** | React Context 사용 가능 | 앱의 Provider | Expo 고유의 DI 구성은 확인되지 않음 |

    수정 이유

    기술 설명 보정: 환경 비교표에 React 19.3의 Context 렌더링과 서버 값 전달 조건을 반영했습니다.

  2. 변경 2

    수정 전

    - 3장의 패턴(A~D)을 제약 없이 그대로 사용합니다. - 서버 상태(API 데이터)는 TanStack Query 같은 캐시 라이브러리로, 의존성(Repository, 클라이언트, 설정)은 Context로 나눕니다([판단]). 두 종류는 변경 빈도와 수명이 다르기 때문입니다. - 참고: Next.js 공식 SPA 가이드는 Next.js로도 SPA 패턴(`use()`+Context, SWR, TanStack Query 시딩, 브라우저 전용 렌더링, 셸로 라우팅 등)을 구현하는 방법을 제공합니다. 해당 가이드의 예제는 vercel-labs/next-spa-patterns 저장소에 실행 가능한 데모로도 공개돼 있습니다[공식].

    수정 후

    - 3장의 props와 Context 구성은 클라이언트 전용 앱에도 적용할 수 있습니다. - 서버에서 조회한 값의 캐시와 재검증은 TanStack Query 같은 도구에 맡기고, 거의 바뀌지 않는 서비스 구현체는 Context로 전달하는 구성을 검토할 수 있습니다. 두 값은 변경 주기와 관리 방법이 다릅니다. [판단] - Next.js의 SPA 가이드는 `use`와 Context, SWR, TanStack Query를 이용하는 예시를 제공합니다. [공식]

    수정 이유

    기술 설명 보정: 클라이언트 전용 앱에 아무런 제약도 없다는 단정을 줄였습니다.

  3. 변경 3

    수정 전

    Next.js 16.3.7 문서(2026-08-25 갱신) 기준입니다. **B-1. Context는 Client Component에서만 가능합니다 [공식]** - 레이아웃과 페이지는 기본적으로 Server Component이며, React Context는 Server Component에서 지원되지 않습니다. - 해결: `children`을 받는 Client Component에서 Context를 만들고 Provider를 렌더링합니다. 그 Provider를 Server Component(`layout.tsx`)에서 import해 사용합니다. - Provider는 트리에서 **가능한 한 깊게** 두라고 권합니다. `<html>` 전체가 아니라 `{children}`만 감싸면 정적 부분을 Next.js가 더 잘 최적화할 수 있습니다. - `'use client'` 파일이 import하는 모듈과 직접 렌더링하는 컴포넌트는 클라이언트 번들에 포함됩니다. 반대로 `children` 등으로 전달된 Server Component는 그 모듈 그래프에 들어가지 않고 서버에서 렌더링된 결과로 전달됩니다. **B-2. Server → Client로 데이터 전달 [공식]** - 가장 단순한 방법은 props입니다. 단, Client Component로 넘기는 props는 React가 **직렬화**할 수 있어야 합니다. 함수·클래스 인스턴스 같은 것은 넘길 수 없다는 뜻이므로, 서버에서 구현체 자체를 Client로 넘겨 DI하는 방식은 쓸 수 없습니다. - 그래서 DI 구현체(`createHttpPostRepository`)는 **Client Component 쪽 Provider 안에서 생성**합니다(3.3 패턴). 서버에서는 직렬화 가능한 설정값(URL 등)만 props로 내려 보내는 식으로 조합합니다([판단]). - 서버에서 가져온 데이터를 여러 Client Component가 읽어야 하면, Promise를 Context에 담아 전달하고 `use()`로 푸는 패턴을 공식 문서가 제시합니다. Server Component가 Promise를 만들어 Client로 전달하면 리렌더 사이에서 안정적이지만, Client Component 안에서 만든 Promise는 렌더마다 재생성됩니다[공식, `use` 참조 문서].

    수정 후

    **B-1. Server Component와 Context [공식]** - 레이아웃과 페이지는 기본적으로 Server Component입니다. Server Component는 Context를 생성하거나 읽을 수 없습니다. React 19.3에서는 `'use client'` 모듈에서 가져온 Context를 렌더링할 수 있습니다. 이 방법이 실제 앱에서 동작하는지는 사용하는 Next.js 버전에서 확인해야 합니다. - 클라이언트 모듈에 `children`을 받는 Provider를 만들고 이를 서버 레이아웃에서 렌더링하는 방식도 사용할 수 있습니다. - Provider는 트리에서 **가능한 한 깊게** 두라고 권합니다. `<html>` 전체가 아니라 `{children}`만 감싸면 정적 부분을 Next.js가 더 잘 최적화할 수 있습니다. - `'use client'` 파일이 import하는 모듈은 클라이언트 번들에 포함됩니다. Server Component를 `children`으로 전달하면 그 컴포넌트를 Client Component가 직접 import하지 않아도 됩니다. **B-2. 서버에서 클라이언트로 값 전달 [공식]** - Server Component에서 Client Component로 전달하는 props는 React가 직렬화할 수 있어야 합니다. 일반적인 함수나 클래스 인스턴스를 구현체로 만들어 그대로 전달하는 방식은 이 조건에 맞지 않습니다. - 3.3절의 HTTP 저장소 구현체는 Client Provider 안에서 만들 수 있습니다. 서버에서 클라이언트로 전달할 설정값은 직렬화 가능한 형태로 준비합니다. [판단] - 여러 Client Component가 서버에서 시작한 같은 비동기 작업을 읽어야 한다면, Promise를 Context로 전달하고 각 컴포넌트에서 `use`로 읽는 공식 예시가 있습니다. 서버에서 만든 Promise를 전달하고 값을 읽는 컴포넌트는 Suspense 안에 둡니다.

    수정 이유

    기술 설명 보정: Server Component의 Context 생성 및 읽기 제한과 React 19.3의 렌더링 허용을 구분했습니다.

  4. 변경 4

    수정 전

    // 소비 컴포넌트 (Suspense로 감싸서 사용)

    수정 후

    // 값을 읽는 Client Component는 Suspense 안에서 사용

    수정 이유

    표현 수정: Promise 값을 읽는 Client Component와 Suspense의 관계를 주석에 명시했습니다.

  5. 변경 5

    수정 전

    - 서버 쪽에서는 `await` 없이 Promise를 시작하고 Provider에 넘기며, 소비 쪽은 `<Suspense>`로 감쌉니다. 같은 요청 안에서 여러 곳이 같은 데이터를 읽으면 `React.cache`로 감싸 호출을 공유합니다[공식]. - 주의(React `use` 문서의 pitfall): 이 패턴을 Server Component와 함께 쓰면, Promise를 다시 가져오려면 그 Promise를 Context에 넣은 Server Component를 다시 가져와야 합니다. 그러므로 Promise를 트리 높은 곳에 넣지 말라고 경고합니다. (이 문구는 번역 사이트 판본에서 확인했으므로 추가 과제 3번에서 원문 재확인을 권합니다.)

    수정 후

    - 이 구성에서는 서버가 Promise를 시작하고 Client Component가 `<Suspense>` 안에서 값을 읽습니다. 같은 요청에서 호출을 재사용해야 한다면 `React.cache`를 사용할 수 있습니다. [공식] - React는 이 방식에서 Promise를 다시 가져올 때 Context에 값을 넣은 Server Component도 다시 가져와야 한다고 설명합니다. 불필요하게 많은 화면을 다시 가져오지 않도록 Promise를 제공하는 위치를 정해야 합니다. [공식]

    수정 이유

    기술 설명 보정: Promise를 다시 가져올 때 갱신해야 하는 Server Component의 위치를 밝혔습니다.

  6. 변경 6

    수정 전

    서버 코드에서의 "DI"는 Context가 아닙니다 [판단]** - Server Component에서는 Hook과 Context를 쓸 수 없으므로, 서버 코드의 의존성은 (1) 함수 인자로 전달, (2) 모듈 단위 팩토리, (3) `React.cache`로 요청 단위 메모이제이션 중 하나로 다루게 됩니다. - Next.js 문서는 `React.cache`가 **현재 요청에만** 유효하다고 명시합니다. `'use cache'` 경계 안에서는 `React.cache`가 별도로 격리된 범위를 가지므로, 바깥에서 저장한 값이 안쪽에서 보이지 않습니다. 값은 함수 인자로 넘겨야 하고, 그 인자가 캐시 키의 일부가 됩니다[공식]. - 이 영역에 대해 Next.js가 "DI 컨테이너"를 권장하는 공식 문서는 이번 조사에서 찾지 못했습니다(추가 과제 1번).

    수정 후

    서버 코드의 의존성 전달 [판단]** - Server Component에서 Context를 읽는 대신, 서버 함수에 의존성을 인자로 전달하거나 요청마다 팩토리 함수로 구현체를 만들 수 있습니다. `React.cache`는 요청 중 호출 결과를 재사용하는 수단이며 DI 컨테이너 자체는 아닙니다. - Next.js 문서는 `React.cache`가 현재 요청에만 유효하다고 설명합니다. 이 보고서의 서버 측 의존성 구성은 Next.js가 공식 DI 패턴으로 제시한 것은 아닙니다.

    수정 이유

    기술 설명 보정: React.cache의 요청 중 재사용과 의존성 주입을 서로 다른 역할로 설명했습니다.

  7. 변경 7

    수정 전

    - **TanStack Query**: App Router 가이드에서 서버에서는 `QueryClient`를 요청마다 새로 만들고, 브라우저에서는 하나를 재사용하는 `getQueryClient()` 패턴을 사용합니다. 서버에서 prefetch한 결과를 `dehydrate` → `HydrationBoundary`로 클라이언트에 넘깁니다. 서버 컴포넌트마다 새 `queryClient`를 만드는 것이 권장 방식이며, `cache()`로 요청 단위 단일 인스턴스를 재사용하는 방식도 허용됩니다. 이 두 라이브러리 모두 "인스턴스를 Provider에서 만들어 주입"하는 DI 형태입니다.

    수정 후

    - **TanStack Query**: App Router 가이드는 서버에서 QueryClient를 새로 만들고 브라우저에서는 이미 만든 QueryClient를 다시 쓰는 예시를 제공합니다. 서버에서 미리 조회한 값을 `dehydrate`와 `HydrationBoundary`로 클라이언트에 전달할 수 있습니다. 서버 컴포넌트마다 QueryClient를 만드는 방식 외에 `cache()`로 요청 안에서 재사용하는 방식도 설명합니다. [공식]

    수정 이유

    기술 설명 보정: 서버 QueryClient 생성과 브라우저 재사용 방식을 공식 예시에 맞게 수정했습니다.

  8. 변경 8

    수정 전

    - **loader/action/middleware**: React Router는 `createContext`(react-router에서 import)로 **타입 안전한 라우터 컨텍스트**를 제공합니다. middleware에서 `context.set(userContext, user)`로 값을 넣고 `loader`에서 `context.get(userContext)`로 읽습니다. 값이 설정되지 않은 컨텍스트를 기본값 없이 읽으면 에러가 납니다[공식]. 이는 **서버측/라우트 실행 계층의 DI 통로**로 쓸 수 있습니다([판단]). - 커스텀 서버를 쓸 때는 `getLoadContext`가 `RouterContextProvider` 인스턴스를 반환해야 하고, 여기에 `db` 같은 의존성을 `set`하는 예시가 문서에 있습니다[공식]. - 주의: React의 `createContext`와 react-router의 `createContext`는 이름이 같지만 별개입니다. 전자는 컴포넌트 트리용, 후자는 요청/응답 생명주기용입니다. 두 개를 혼동하지 않도록 import 경로를 명확히 합니다. - 주의: middleware 활성화 방식(future 플래그 필요 여부, `context` 파라미터 타입 변경)이 문서 판본마다 달라 보였습니다. 도입 시 사용 중인 버전의 문서로 확인해야 합니다(추가 과제 4번).

    수정 후

    - **loader, action, middleware**: React Router의 `createContext`로 라우터 작업에서 읽고 쓸 값을 정의합니다. middleware의 `context.set(userContext, user)`와 loader의 `context.get(userContext)`가 공식 예시입니다. 기본값 없이 생성한 항목을 설정하기 전에 읽으면 오류가 납니다. 의존성을 전달하는 데에도 사용할 수 있습니다. [공식, 판단] - 커스텀 서버를 쓴다면 `getLoadContext`에서 `RouterContextProvider` 인스턴스를 반환하고 필요한 값을 설정할 수 있습니다. [공식] - React의 `createContext`는 컴포넌트가 읽는 값을 정의합니다. React Router의 동명 함수는 loader와 middleware 등이 읽는 값을 정의합니다. - React Router v7에서 middleware를 사용하는 경우 `future.v8_middleware` 설정이 필요합니다. 프레임워크 모드와 데이터 모드의 설정 위치는 각각 다릅니다. [공식]

    수정 이유

    기술 설명 보정: React Router의 두 Context API를 구분하고 v7 middleware 설정 조건을 밝혔습니다.

  9. 변경 9

    수정 전

    - React 공식 문서는 Expo를 네이티브 앱용 프레임워크로 권장합니다[공식]. 네이티브에서도 컴포넌트는 React이므로 3장의 Context + 훅 패턴은 원칙적으로 동일하게 적용될 것으로 보이나([판단]), 이번 조사에서 Expo/RN 고유의 DI 관련 공식 자료는 확인하지 못했습니다(추가 과제 6번).

    수정 후

    - React는 새 네이티브 앱을 시작할 때 Expo를 권장합니다. Context와 훅을 사용한다는 기본 개념은 같지만, Expo Router에서 의존성을 제공할 위치는 여기서 확인되지 않았습니다. [공식, 판단]

    수정 이유

    기술 설명 보정: Expo Router에서 의존성을 제공할 위치는 확인되지 않았음을 명시했습니다.

6. 성능과 렌더링

  1. 변경 1

    수정 전

    **Context 값이 바뀌면 소비자는 리렌더됩니다.** React 문서는 `memo`로 렌더를 건너뛰어도 Context로 전달된 새 값은 자식에게 전달된다고 명시합니다[공식]. 2. **값의 참조 안정성**: Provider의 `value`에 매 렌더 새 객체를 만들어 넣으면 모든 소비자가 리렌더됩니다. 공식 문서에는 객체·함수를 Context로 전달할 때 `useMemo`/`useCallback`으로 최적화하는 절이 있습니다. 3.3 패턴처럼 `useState(() => ...)`로 한 번만 만든 의존성 객체는 이 문제를 피합니다. 3. **React Compiler**: 빌드 타임에 자동으로 메모이제이션을 적용하는 도구로, 공식 문서는 사용 시 수동 `useMemo`/`useCallback`/`memo`를 제거할 수 있다고 설명합니다. 수동 메모이제이션을 남기면 컴파일러가 이를 분석하고, 자동 추론 결과와 맞지 않으면 그 컴포넌트의 최적화를 건너뜁니다[공식]. 도입 범위와 Context 패턴과의 상호작용은 프로젝트에서 직접 검증해야 합니다(추가 과제 2번). 4. **Provider 분리**: 의존성(거의 안 바뀜)과 상태(자주 바뀜)를 서로 다른 Context로 분리하면 리렌더 범위를 줄일 수 있습니다. 공식 reducer+Context 예제가 상태/dispatch를 분리하는 것과 같은 원리입니다. 5. **`use()`와 `useContext`**: `use`는 조건문·반복문 안에서도 호출할 수 있는 점이 Hook과 다릅니다. 단, 컴포넌트나 Hook 안에서만 호출해야 합니다[공식].

    수정 후

    **Context 값의 변경**: Provider의 `value`가 달라지면 그 Context를 읽는 컴포넌트가 다시 렌더링됩니다. `memo`는 Context 변경으로 인한 렌더링을 막지 못합니다. [공식] 2. **객체 참조**: Provider가 렌더링될 때마다 새 객체를 `value`로 전달하면, 내용이 같더라도 다른 값으로 취급될 수 있습니다. React 문서는 객체와 함수를 전달할 때 `useMemo`와 `useCallback`을 사용하는 최적화 예시를 제공합니다. 3.3절처럼 Provider가 유지되는 동안 의존성 객체를 한 번 생성할 수도 있습니다. [공식] 3. **React Compiler**: 컴포넌트와 훅을 자동으로 메모이제이션합니다. 기존의 수동 메모이제이션을 제거하려면 동작을 확인해야 하며, 필요한 곳에는 `useMemo`와 `useCallback`을 계속 사용할 수 있습니다. Compiler가 Context를 사용하는 이 예시에 주는 효과는 별도 측정이 필요합니다. [공식] 4. **Context 분리**: 자주 바뀌는 상태와 거의 바뀌지 않는 의존성을 분리하면 변경의 영향을 받는 컴포넌트를 줄일 수 있습니다. React의 상태용 Context와 dispatch용 Context 분리 예시가 참고가 됩니다. [공식, 판단] 5. **`use`와 `useContext`**: `use`는 조건문과 반복문에서도 호출할 수 있습니다. 다만 컴포넌트나 다른 Hook 안에서 호출해야 합니다. [공식]

    수정 이유

    기술 설명 보정: Context 변경의 영향을 받는 컴포넌트와 Compiler 최적화의 확인 조건을 구체화했습니다.

7. 안티패턴

  1. 변경 1

    수정 전

    1. **모든 것을 Context로 올리기**: 공식 문서가 먼저 props와 컴포넌트 추출을 시도하라고 권합니다. 데이터 흐름이 숨겨지고 리렌더 범위가 커집니다. 2. **Provider 누락 시 기본값으로 조용히 통과**: 3.1처럼 예외를 던져 즉시 발견되게 합니다([판단]). 3. **하나의 거대한 `AppContext`에 모든 서비스·상태 혼합**: 변경 빈도가 다른 값을 분리합니다. 4. **Next.js에서 서버로 구현체를 만들어 props로 전달**: 직렬화 불가로 실패합니다. Client Provider 안에서 생성하고, 서버에서는 직렬화 가능한 설정만 전달합니다. 5. **Next.js에서 스토어/QueryClient를 모듈 전역에 두고 서버에서 공유**: 요청 간 상태가 섞입니다(Zustand·TanStack Query 문서 공통 경고). 6. **`'use client'`를 최상단 레이아웃 전체에 남발**: 클라이언트 번들이 커집니다. 상호작용이 필요한 리프 컴포넌트에만 붙이라고 문서가 안내합니다. 7. **인터페이스 하나에 구현체 하나뿐인데 추상화 계층 추가**: 교체·테스트 요구가 없으면 과잉 설계입니다([판단]). 8. **`asChild` 사용 시 props/ref 미전달**: Radix 컴포넌트가 동작하지 않거나 접근성이 깨집니다.

    수정 후

    1. **Context부터 도입**: React는 먼저 props와 컴포넌트 추출로 전달 단계를 줄일 수 있는지 살피라고 안내합니다. 2. **누락된 Provider를 기본값으로 감춤**: Provider가 반드시 필요한 서비스라면 3.1절처럼 누락을 오류로 드러냅니다. 기본값 자체가 유효한 경우에는 이 규칙을 적용하지 않습니다. [판단] 3. **변경 주기가 다른 값을 한 Context에 혼합**: 상태가 바뀔 때 그 상태가 필요하지 않은 컴포넌트까지 다시 렌더링될 수 있습니다. 값의 성격에 따라 분리합니다. [판단] 4. **직렬화할 수 없는 서버 구현체를 Client Component의 props로 전달**: 일반적인 함수와 클래스 인스턴스는 이 방식으로 전달하지 않습니다. 클라이언트에서 구현체를 만들거나 서버 함수가 필요한 작업을 수행하게 합니다. [공식, 판단] 5. **서버 요청 사이에 스토어 또는 QueryClient 공유**: 사용자별 상태와 캐시가 섞일 수 있으므로 요청마다 필요한 인스턴스를 만듭니다. [공식] 6. **필요 이상으로 높은 위치에 `'use client'` 선언**: 선언한 모듈이 가져오는 코드도 클라이언트 번들에 포함됩니다. 상호작용이 필요한 부분과 Provider가 필요한 위치를 기준으로 배치합니다. [공식] 7. **사용 목적 없이 추상화 추가**: 구현체 교체나 테스트에서 대체할 필요가 없다면 인터페이스와 Provider가 필요한지 다시 봅니다. [판단] 8. **`asChild`의 자식 컴포넌트가 props나 ref를 전달하지 않음**: 동작과 포커스 관리가 깨질 수 있습니다. [공식]

    수정 이유

    기술 설명 보정: Provider 누락, 값 직렬화와 추상화에 관한 주의사항의 적용 조건을 밝혔습니다.

8. 환경과 전달 방식

  1. 변경 1

    수정 전

    ## 8. 환경별 선택 흐름 (요약) 1. 새 프로젝트인가? → 공식 문서는 프레임워크(Next.js App Router / React Router v7 / Expo)를 권장합니다. 서버 기능이 전혀 필요 없고 학습·소규모라면 Vite 등 빌드 도구도 공식 문서에 있습니다. 2. 서버 컴포넌트(RSC)를 쓰는가? - 예 → 5.3(Next.js)을 따릅니다. Provider는 `'use client'` 파일에 두고 깊게 배치하며, 서버 의존성은 Context가 아닌 방식으로 다룹니다. - 아니오 → 5.2/5.4로 진행합니다. 3. props 전달이 깊어졌는가? → `children` 슬롯으로 중간 층을 없애는 것이 먼저이고, 그다음 Context입니다. 4. 구현체가 여러 개이거나 테스트에서 교체가 필요한가? → 3.2 패턴(인터페이스 + Context + 훅)을 적용합니다. 5. 동작은 재사용하고 렌더링은 교체하고 싶은가? → 4.2 `asChild` 또는 4.1 슬롯을 씁니다. 6. 새 종류가 계속 추가되는 목록/분기가 있는가? → 4.4 렌더러 레지스트리를 검토합니다. 7. 컨테이너 라이브러리가 정말 필요한가? → 위 1~6으로 해결되는지 먼저 확인합니다. 그래도 필요하다면 유지보수 상태를 확인한 뒤 도입합니다.

    수정 후

    ## 8. 환경과 전달 방식 선택 1. 실행 환경을 정합니다. React는 새 앱에 프레임워크를 권장하고, 직접 구성할 때는 Vite 등의 빌드 도구도 안내합니다. RSC를 사용한다면 선택한 프레임워크의 서버와 클라이언트 규칙을 확인합니다. 2. 값을 쓰지 않는 중간 컴포넌트가 props를 전달하기만 한다면 컴포넌트를 추출하거나 `children`으로 JSX를 전달할 수 있는지 살핍니다. 여러 곳에서 값을 읽어야 한다면 Context를 검토합니다. 3. 구현체를 교체할 필요가 있다면 props나 함수 인자로 충분한지 확인합니다. 여러 컴포넌트가 같은 구현체를 읽을 때는 3.2절의 Context와 훅 구성을 검토합니다. 4. 기존 컴포넌트의 내용을 바꾸려면 슬롯을, 렌더링 요소를 바꾸려면 바꿀 컴포넌트가 `asChild`를 받는지 확인합니다. 항목 유형이 계속 늘어난다면 4.4절의 등록표도 후보입니다. 5. DI 컨테이너를 도입하기 전에 필요한 교체 방식과 사용하려는 라이브러리의 유지보수 상태를 확인합니다.

    수정 이유

    표현 수정: 일률적인 선택 지시 대신 필요한 값과 실행 환경에 따라 검토할 순서를 제시했습니다.

9. 적용 조건

  1. 변경 1

    수정 전

    ## 9. 도입 체크리스트 1. Provider 파일은 `providers.tsx` 하나로 모으고(Composition Root), Next.js에서는 `'use client'`를 붙입니다. 2. 인터페이스(포트)는 `domain/`, 구현체는 `infra/`처럼 분리해 훅이 구현체를 import하지 않게 합니다. 3. 모든 Context 훅은 Provider 누락 시 예외를 던집니다. 4. Provider의 `value`는 참조가 안정적이어야 합니다(`useState` lazy 초기화, `useMemo`, 또는 React Compiler). 5. 테스트는 `renderHook`/`render`의 `wrapper`로 fake 의존성을 주입하고, 테스트마다 캐시 인스턴스(QueryClient 등)를 새로 만듭니다. 6. 서버 전용 모듈에는 `server-only`를 import하고, 클라이언트 전용 모듈에는 `client-only`를 사용합니다. 7. 요청 간 공유되면 안 되는 인스턴스(스토어, QueryClient)는 서버에서 요청마다 새로 만듭니다.

    수정 후

    ## 9. 적용할 때 확인할 조건 1. Provider가 필요한 컴포넌트만 감싸고, Client Component가 import하는 모듈이 무엇인지 확인합니다. 2. 구현체 교체가 목적이라면 훅이 구체 구현을 import하지 않는지 확인합니다. `domain/`과 `infra/`는 가능한 파일 배치 예시이며 필수 구조는 아닙니다. 3. 기본값이 유효하지 않은 Context라면 Provider가 없을 때 오류를 드러냅니다. 4. Provider의 `value`가 매 렌더 새 객체가 되는지 확인합니다. 실제로 불필요한 렌더링이 발생한다면 생성 위치나 메모이제이션을 조정합니다. 5. 테스트에서 QueryClient 같은 캐시 인스턴스를 쓴다면 테스트 사이에 공유되지 않게 합니다. 6. Next.js에서 서버 전용 코드가 클라이언트에 포함되면 안 되는 경우 `server-only`를 사용합니다. 브라우저 API를 쓰는 모듈은 서버에서 실행되지 않도록 구성합니다. 7. 서버에서 사용자별 상태를 담는 스토어나 QueryClient를 사용한다면 요청 사이에 공유하지 않습니다.

    수정 이유

    기술 설명 보정: 특정 파일 배치와 모든 Context 훅의 예외 처리를 필수 규칙으로 제시하지 않도록 수정했습니다.

10. 출처

  1. 변경 1

    수정 전

    - React (구 문서, 갱신 중단) — Composition vs Inheritance: https://legacy.reactjs.org/docs/composition-vs-inheritance.html - Next.js — Server and Client Components (v16.3.7, 2026-08-25 갱신): https://nextjs.org/docs/app/getting-started/server-and-client-components - Next.js — Fetching Data (`React.cache`, `use` API): https://nextjs.org/docs/app/getting-started/fetching-data - Next.js — 단일 페이지 애플리케이션 가이드 (`use` + Context Provider): https://nextjs.org/docs/app/guides/single-page-applications - Next.js — `use cache` 지시어 (React.cache 격리): https://nextjs.org/docs/app/api-reference/directives/use-cache - React Router — middleware 가이드: https://reactrouter.com/how-to/middleware

    수정 후

    - React — React Compiler 1.0: https://react.dev/blog/2025/10/07/react-compiler-1 - React — React 19.3: https://react.dev/blog/2026/09/09/react-19-3 - React (구 문서, 갱신 중단) — Composition vs Inheritance: https://legacy.reactjs.org/docs/composition-vs-inheritance.html - Next.js — Server and Client Components: https://nextjs.org/docs/app/getting-started/server-and-client-components - Next.js — Fetching Data (`React.cache`, `use` API): https://nextjs.org/docs/app/getting-started/fetching-data - Next.js — 단일 페이지 애플리케이션 가이드 (`use` + Context Provider): https://nextjs.org/docs/app/guides/single-page-applications - Next.js — `use cache` 지시어 (React.cache 격리): https://nextjs.org/docs/app/api-reference/directives/use-cache - React Router — middleware 가이드: https://reactrouter.com/how-to/middleware - React Router — Updating from v7: https://reactrouter.com/upgrading/v7

    수정 이유

    기술 설명 보정: React 19.3과 React Router 공식 출처를 보태고 확인되지 않은 Next.js 문서 버전을 지웠습니다.

11. 적용 환경별 확인 사항

  1. 변경 1

    수정 전

    ## 11. 추가 과제 (불확실 / 추가 확인 필요) 1. **Next.js 서버 코드의 DI 공식 가이드 부재**: RSC/Route Handler/Server Action에서 서비스 계층을 교체 가능하게 만드는 공식 권장 패턴을 찾지 못했습니다. 현재 5.3의 B-4는 함수 인자·팩토리·`React.cache`라는 일반 기법을 조합한 작성자 판단입니다. Next.js·React 팀 자료와 실제 대형 오픈소스(예: 공식 예제 저장소)로 검증이 필요합니다. 2. **React Compiler와 Context 패턴의 상호작용**: 컴파일러가 Provider `value` 안정화를 어디까지 대체하는지, 그리고 `use()`+Context 패턴에서의 동작은 이번 조사에서 직접 확인하지 못했습니다. 도입 전에 컴파일러 플레이그라운드와 프로젝트 빌드로 검증해야 합니다. 3. **`use` 문서의 Server Component + Context pitfall 원문 확인**: "Promise를 트리 높은 곳에서 Context에 넣지 말라"는 경고와 "Server Component에서는 `use`로 Context를 읽을 수 없다"는 문구는 react.dev 번역 사이트 판본에서 확인했습니다. 원문(react.dev/reference/react/use) 최신본에서 동일한지 재확인이 필요합니다. 4. **React Router middleware 활성화 방식**: 프레임워크 모드에서 future 플래그가 필요한지, 데이터 모드에서 `context` 타입 보강(`Future` 인터페이스)이 필요한지가 문서 판본마다 다르게 보였습니다. 사용 버전의 문서로 확인해야 합니다. 5. **Zustand Next.js 가이드 개정 예고**: 가이드 상단에 곧 개정한다는 안내가 있었습니다. 도입 시점에 최신 권장 패턴(디스커션 #2740 결과)을 다시 확인해야 합니다. 6. **Expo / React Native**: 네이티브 환경에서의 Context 기반 DI, 라우터(Expo Router) 통합에 대한 공식 자료는 조사하지 못했습니다. 7. **외부 스토어 기반 주입**: `useSyncExternalStore`나 Zustand·Redux Toolkit으로 의존성을 주입하는 패턴, 그리고 Context 대비 리렌더 특성 비교는 공식 자료로 확인하지 않았습니다. 8. **대규모 프론트엔드 폴더 구조 사례**: Feature-Sliced Design, bulletproof-react 등에서 DI·OCP를 어떻게 구조화하는지는 1차 자료로 확인하지 못했습니다. 3.2의 `domain/infra` 분리는 [판단]입니다. 9. **컴파운드 컴포넌트 패턴**: OCP 기법으로 자주 언급되는 컴파운드 컴포넌트 패턴(Context로 부모-자식 협력)은 이번에 공식 문서 근거를 확보하지 못해 본문에서 다루지 않았습니다. 10. **DI 컨테이너 라이브러리의 현재 유지보수 상태**: 3.5의 라이브러리들은 이번 조사에서 최신 릴리스와 React 19 호환 여부를 확인하지 못했습니다. 도입을 고려한다면 npm 릴리스, 이슈, React 19 호환 여부를 직접 확인해야 합니다. 11. **`asChild` 패턴의 React 19 `ref` 변화**: Radix 문서 예시는 `forwardRef`를 사용합니다. React 19에서 `ref`를 prop으로 받는 방식과 Radix 최신 문서의 권장 형태는 이번에 확인하지 못했습니다.

    수정 후

    ## 11. 적용 환경에 따라 달라지는 항목 - **Next.js와 React 19.3 조합:** Server Component가 클라이언트 모듈의 Context를 직접 렌더링하는 React 19.3의 방식을 어느 Next.js 버전에서 사용할 수 있는지는 확인되지 않았습니다. 사용할 수 없는 버전에서는 Client Provider 컴포넌트를 사용합니다. - **React Compiler:** 3.3절 Provider의 `value`가 실제 빌드에서 어떻게 처리되는지는 Compiler 설정과 컴파일 대상에 따라 다릅니다. 성능 판단에는 렌더링 횟수 측정이 필요합니다. - **Expo:** Expo Router를 비롯한 실행 환경에서 의존성을 어디서 생성하고 전달할지는 이 보고서의 사례만으로 결정할 수 없습니다. - **DI 라이브러리:** 3.5절 라이브러리의 최신 릴리스와 React 19 호환 여부는 확인되지 않았습니다. - **Radix와 React 19:** `asChild`를 적용하는 자식 컴포넌트의 ref 전달 방식은 사용하는 Radix 버전의 문서에 맞춰야 합니다.

    수정 이유

    기술 설명 보정: 이미 확인한 항목을 추가 과제에서 제외하고 실제 적용 버전에 따라 달라지는 항목을 남겼습니다.