diff --git "a/8\354\236\245-\353\246\254\354\225\241\355\212\270 \353\215\260\354\235\264\355\204\260 \352\264\200\353\246\254/\353\260\225\353\263\264\353\235\274.md" "b/8\354\236\245-\353\246\254\354\225\241\355\212\270 \353\215\260\354\235\264\355\204\260 \352\264\200\353\246\254/\353\260\225\353\263\264\353\235\274.md" new file mode 100644 index 0000000..9405b3c --- /dev/null +++ "b/8\354\236\245-\353\246\254\354\225\241\355\212\270 \353\215\260\354\235\264\355\204\260 \352\264\200\353\246\254/\353\260\225\353\263\264\353\235\274.md" @@ -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(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
Loading...
+ } + + return ( +
+

{user.name}

+
+ ); +} +``` + +🔍 그래서 저렇게 하면 뭐가 문제인데 ? +- 백엔드랑 주고 받는 데이터의 형식이 프론트에서 실제 노출하는 데이터 형식과 다를 수 있음. + - 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 { + 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 } +function PageLayout({ userName }: { userName:string }){ return } +function Toolbar({ userName }: { userName:string }){ return } +function UserMenu({ userName }: { userName:string }){ return } +function Avatar({ label }:{label:string}){ return {label} } + +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(null); + +export function UserProvider({ children }: React.PropsWithChildren) { + // 실제로는 fetch + ACL 매핑을 통해 User 확보 + const value = useMemo(() => ({ name: '보라', isVip: true }), []); + return {children}; +} + +// 셀렉터 헬퍼 +export function useUser(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 {name}; +}