App.tsx
└── NonContext.tsx
└── GrandParent.tsx
└── Parent.tsx
└── Child.tsx
└── GrandChild.tsx
하위 컴포넌트로 데이터를 넘기기 위해 중간 컴포넌트들도 모두 props를 받아야 함
중간 컴포넌트가 props를 직접 쓰지도 않는데 단지 전달만 함
이런 현상을 👉 props drilling이라고 부름
유지보수 어려움
→ 중간 컴포넌트를 건드릴 일 없이 값만 바꾸고 싶은데도, 파일 여러 개를 수정해야 함
재사용성 낮음
→ 중간 컴포넌트가 특정 props에 의존하게 되면 재사용이 어려워짐
타입 변경 시 불편함 (TypeScript)
→ props의 이름이나 타입이 바뀌면 전달 경로의 모든 interface를 수정해야 함
→ 타입스크립트는 정적 타입 언어라 컴파일 에러 발생
여러 컴포넌트에서 공유해야 하는 데이터는 전역 저장소처럼 Context에 저장하고,
하위 컴포넌트에서는 props 없이도 바로 꺼내 쓰게 해준다.
| 단계 | 설명 |
|---|---|
1. createContext() | 공유할 데이터의 틀 생성 |
2. Context.Provider | 값을 실제로 공급함 (보통 최상단에서 감쌈) |
3. useContext() | 하위 컴포넌트에서 값 꺼내 쓰기 |
| 장점 |
|---|
| props 없이 하위 컴포넌트에서 전역 데이터 사용 가능 |
| 중간 컴포넌트는 props를 몰라도 됨 |
| 코드가 간결하고 명확해짐 |
| 상태와 UI가 느슨하게 연결되어, 유지보수성 증가 |
import { createContext, JSX, useState } from 'react';
/*
1. Context 생성 -> 공유할 값의 타입과 초기값을 정의
여기서 createContext로 생성한 객체를 useContext로 가져와서 사용
*/
const CountContext = createContext({
count: 0,
plusCount: () => {},
});
/*
2. 타입 정의
*/
interface Props {
children: JSX.Element | JSX.Element[];
}
/*
3. CountProvider 컴포넌트 정의
*/
const CountProvider = ({ children }: Props): JSX.Element => {
const [count, setCount] = useState(0);
const plusCount = (): void => {
setCount(count + 1);
};
return (
<CountContext.Provider
value={{
count,
plusCount,
}}
>
{children}
</CountContext.Provider>
);
};
/*
4. CountContext와 CountProvider를 export
*/
export { CountContext, CountProvider };
const CountContext = createContext({
count: 0,
plusCount: () => {},
});
이 CountContext는 사실 내부적으로 이런 두 가지 속성을 가진 객체
{
Provider: 리액트 제공자 컴포넌트,
Consumer: 리액트 구독자 컴포넌트
}
그래서 우리는 CountContext.Provider처럼 점(.)으로 접근해서
Provider 컴포넌트를 꺼내서 사용하는 거