Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions 8장-리액트 데이터 관리/박보라.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# 8장 리액트 데이터 관리

> 이 장은 “비즈니스 로직 누수”, Prop Drilling, 그리고 이를 줄이기 위한 ACL(오류 방지 계층)·Context 전략을 다룬다.

## 8.1 비즈니스 로직 누수 현상

🤔 비즈니스 로직이란?
- 어플리케이션을 운영하기 위한 정책, 계산 로직, 절차 등을 의미함

🐦‍🔥 비즈니스 로직 누수란?
- 비즈니스 로직과 관련 없는 컴포넌트나 어플리케이션 영역으로 흘러 들어가는 현상
- 로직과 강한 결합을 나타내는 컴포넌트를 만들어냄 > 테스트, 유지보수를 어렵게 함
- 비즈니스 로직이 어플리케이션 전반에 걸쳐 흩어져 있으면 코드 중복과 일관성이 부족해지는 현상이 발생한다.

🫠 가장 흔한 예 : presentational 한 컴포넌트에서 데이터 변경하기

```tsx
import React from "react";
import { User } from "../types";

export default function UserProfile({ user }: { user: User }) {
const [user, setUser] = useState<User | null>(null);

useEffect(() => {
async function fetchUser() {

cosnt response = await fetch(`/api/users/${id}`);
const data = await response.json();

setUser({
id: data.user_identifiaction,
name: data.user_full_name,
isPremium: data.is_premium_user,
subscription: data.subscription_details.level,
expire: data.subscription_details.expiry,
});
}

fetchUser();
}, [id]):


if(!user) {
return <div>Loading... </div>
}

return (
<div data-testid="user-profile">
<h1>{user.name}</h1>
</div>
);
}
```

🔍 그래서 저렇게 하면 뭐가 문제인데 ?
- 백엔드랑 주고 받는 데이터의 형식이 프론트에서 실제 노출하는 데이터 형식과 다를 수 있음.
- xml 데이터를 내부 타입으로 변환하기도 하고, GraphQL 엔드포인트를 통해 받은 데이터와 동일한 형태의 내부타입으로 변환하는 등..
- 코드 영역에서 데이터 변환이 필요한데, 코드마다 이런 변환 작업이 들어가면, 유지보수가 힘들고 놓치는 부분 발생.
- 백엔드에서 `null`, `undefined` 같은 빈 값 데이터가 왔을 경우의 유저에게 노출되는 UI 처리를 개별적으로 해야 함.

## 8.2 ACL(오류 방지 계층, Anti-Corruption Layer)

- 외부/불안정 소스(REST/3rd-party)에서 오는 형식/명세 차이를 “우리 앱 내부 모델”로 격리/정규화해 로직 누수를 막는다.
- 캐시 처리, 오류 변화과 같은 여러 문제를 처리하는 전략 계층으로 활용
- 여러 로직이 분산되는걸 막을 수 있음.(중앙 집중화)


예시 코드 — 외부 API → 내부 모델 변환

```ts
// external/api.ts (외부 응답 예시)
export type RawUser = { id: string; vip_flag?: 0|1; name?: string | null };

// acl/userMapper.ts (ACL: 외부 → 내부 정규화)
export type User = { id: string; isVip: boolean; name: string };

export function mapUser(raw: RawUser): User {
return {
id: raw.id,
isVip: raw.vip_flag === 1,
name: raw.name ?? '손님',
};
}

// app/repos/userRepo.ts (ACL을 경유)
import { mapUser, User } from '@/acl/userMapper';
export async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`).then(r => r.json());
return mapUser(res); // 내부로는 항상 User 보장
}
```

- ACL은 런타임 가드(zod/io-ts)와 함께 쓰면 효과적이다.
- UI/서비스는 User만 알면 되므로 외부 필드 변경에도 안정적이다.

8.3 Prop Drilling 문제 살펴보기

- 깊은 하위에서 필요한 값/행동을 위해 “중간 컴포넌트가 관심 없는 props”를 계속 전달. 유지보수/가독성/재사용성 저하. (항목 근거)

예시 코드 — Drilling이 심한 구조

// App -> PageLayout -> Toolbar -> UserMenu -> Avatar
export function App(){ return <PageLayout userName="보라" /> }
function PageLayout({ userName }: { userName:string }){ return <Toolbar userName={userName}/> }
function Toolbar({ userName }: { userName:string }){ return <UserMenu userName={userName}/> }
function UserMenu({ userName }: { userName:string }){ return <Avatar label={userName}/> }
function Avatar({ label }:{label:string}){ return <span>{label}</span> }

8.4 Context API로 Prop Drilling 완화

해결책: 공통 상태/서비스를 Context로 노출해 중간 전달 제거.

주의: Context 값 변경은 구독 트리 전체 재렌더 유발 가능—분할/메모·셀렉터 패턴 고려. (장 개요·문맥)
FlipHTML5

예시 코드 — Context + Selector 최소화

import React, { createContext, useContext, useMemo } from 'react';

type User = { name: string; isVip: boolean };
const UserCtx = createContext<User | null>(null);

export function UserProvider({ children }: React.PropsWithChildren) {
// 실제로는 fetch + ACL 매핑을 통해 User 확보
const value = useMemo<User>(() => ({ name: '보라', isVip: true }), []);
return <UserCtx.Provider value={value}>{children}</UserCtx.Provider>;
}

// 셀렉터 헬퍼
export function useUser<T>(selector: (u: User) => T): T {
const u = useContext(UserCtx);
if (!u) throw new Error('UserProvider 누락');
return selector(u);
}

// 소비자: 필요한 조각만 뽑기
export function Avatar() {
const name = useUser(u => u.name);
return <span>{name}</span>;
}