Context 란


Props의 데이터 전달 방식은 서비스의 부모가 커지고 컴포넌트 간의 관계가 복잡해질 경우 데이터를 전달하기 매우 비효율적이게 된다.
이 비효율을 Context 를 통해서 해결할 수 있다.

이런식으로 Context도 분리해서 관리할 수 있다.

Props 방식의 App.jsx
import './App.css'
import Header from "./components/Header"
import Editor from "./components/Editor"
import List from "./components/List"
import {
useState,
useRef,
useReducer,
createContext
} from 'react'
const mockData = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "청소하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "빨래하기",
date: new Date().getTime(),
},
];
function reducer(state, action){
switch(action.type){
case 'CREATE': return [action.data, ...state];
case 'UPDATE': return state.map((item) => item.id === action.targetId? {...item, isDone: !item.isDone}:item);
case 'DELETE': return state.filter((item)=> item.id !== action.targetId);
default: return state;
}
}
export const TodoContext = createContext(); //다른 컴포넌트에서 자유롭게 Context 사용 가능
console.log(TodoContext);
function App() {
//상태를 관리할 때, 배열안에 객체가 들어가는 복잡한 구조 같은것을 사용한다면 일반적으로 useReducer를
//카운팅 처럼 단순 상태를 관리한다면 useState 를 사용한다.
const [todos, dispatch] = useReducer(reducer, mockData);
const idRef = useRef("3");
const onCreate = (content) => {
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
},
});
}
const onUpdate = (targetId) => {
console.log(targetId);
dispatch({
type: "UPDATE",
targetId: targetId,
});
}
const onDelete = (targetId) => {
dispatch({
type: "DELETE",
targetId: targetId,
});
}
return (
<div className='App'>
<Header />
<Editor onCreate={onCreate} />
<List todos={todos} onUpdate={onUpdate} onDelete={onDelete} />
</div>
);
}
export default App
Context 방식의 App.jsx
import {
...
,createContext
} from 'react'
const TodoContext = createContext();
function App() {
...
return (
<div className='App'>
<Header />
<TodoContext.Provider
value={{
todos,
onCreate,
onUpdate,
onDelete
}}
>
<Editor onCreate={onCreate} />
<List
todos={todos}
onUpdate={onUpdate}
onDelete={onDelete}
/>
</TodoContext.Provider>
</div>
);
}
export default App
App 컴포넌트가 리랜더링 될 때마다 context가 변하면 안되기 때문에 App 컴포넌트 밖에 선언한다.

기존에 넘겨줘야 했던 데이터들(onCreate, todos 등)이 Context.provider 내에 선언된 것을 확인 할 수 있다.

그러면 기존에 props를 이용해서 데이터를 전달받던 컴포넌트들도 수정해줘야한다.
Editor 컴포넌트를 참고하자
import {
...
,createContext
} from 'react'
export const TodoContext = createContext(); //다른 컴포넌트에서 자유롭게 Context 사용 가능
function App() {
...
<div className='App'>
<Header />
<TodoContext.Provider
value={{
todos,
onCreate,
onUpdate,
onDelete
}}
>
<Editor />
<List />
</TodoContext.Provider>
</div>
);
}
export default App
이런식으로 선언을 해두고
export const TodoContext = createContext(); //다른 컴포넌트에서 자유롭게 Context 사용 가능
다른 컴포넌트에서는 호출해서 사용할 수 있다.
import { TodoContext } from '../App';
Props 방식의 Editor.jsx
import './Editor.css'
import { useState, useRef } from 'react';
const Editor = ({onCreate}) => {
...
};
export default Editor;
Context 방식의 Editor.jsx
import './Editor.css'
import { useState, useRef, useContext } from 'react';
import { TodoContext } from '../App';
const Editor = () => {
const { onCreate } = useContext(TodoContext);
...
}
Context 를 적용했더니 이전의 memo 최적화 부분이 풀렸다 왜 일까?? Context 분리하기 장을 참고하자

Context 분리하기
TodoContext,Provider 도 결국 App 컴포넌트의 자식이다.
때문에 todos, onCreate 같은 App 컴포넌트의 Props가 바뀌면 결국 TodoContext.Provider 컴포넌트도 리랜더링 되고 그 자식들인 Editor, List, TodoItem 모두 리랜더링 된다.

TodoItem 에는 memo를 적용했는데도 불구하고 왜 리랜더링이 발생하지?
export default memo(TodoItem);
App이 리랜더링 될경우 TodoContext.Provider 객체 자체가 다시 생성된다.
<TodoContext.Provider
value={{
todos,
onCreate,
onUpdate,
onDelete
}}
>
...
</TodoContext.Provider>
때문에 그대로 TodoItem에서의 context 객체도 다시 생성된다, 결국 memo를 적용해도 최적화가 풀린다,
const TodoItem = ({id, isDone, content, date}) => {
const { onUpdate, onDelete } = useContext(TodoContext);
...
}
이럴 경우 context를 한번 더 분리해야 한다.


위 그림 대로 App 컴포넌트에서 context를 2개로 분리하자
App.jsx
import './App.css'
import Header from "./components/Header"
import Editor from "./components/Editor"
import List from "./components/List"
import {
useMemo,
useState,
useRef,
useReducer,
createContext
} from 'react'
//2개로 분리
export const TodoStateContext = createContext(); //변할 수 있는 값
export const TodoDispatchContext = createContext(); //변할 수 없는 값
console.log(TodoContext);
const mockData = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "청소하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "빨래하기",
date: new Date().getTime(),
},
];
function reducer(state, action){
switch(action.type){
case 'CREATE': return [action.data, ...state];
case 'UPDATE': return state.map((item) => item.id === action.targetId? {...item, isDone: !item.isDone}:item);
case 'DELETE': return state.filter((item)=> item.id !== action.targetId);
default: return state;
}
}
function App() {
//상태를 관리할 때, 배열안에 객체가 들어가는 복잡한 구조 같은것을 사용한다면 일반적으로 useReducer를
//카운팅 처럼 단순 상태를 관리한다면 useState 를 사용한다.
const [todos, dispatch] = useReducer(reducer, mockData);
const idRef = useRef("3");
const onCreate = (content) => {
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
},
});
}
const onUpdate = (targetId) => {
console.log(targetId);
dispatch({
type: "UPDATE",
targetId: targetId,
});
}
const onDelete = (targetId) => {
dispatch({
type: "DELETE",
targetId: targetId,
});
}
const memoizedDispatch = useMemo(() => {
return { onCreate, onUpdate, onDelete};
}, []); //2번째 인자는 빈 배열을 적용해서, App 컴포넌트에 마운트 된 이후에는 재생성되지 않도록 한다.
return (
<div className='App'>
<Header />
<TodoStateContext.Provider value={todos}>
<TodoDispatchContext.Provider
value={memoizedDispatch}
>
<Editor />
<List />
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
</div>
);
}
export default App
Editor.jsx
import { TodoDispatchContext } from '../App.jsx';
const Editor = () => {
const { onCreate } = useContext(TodoDispatchContext);
...
}
List.jsx
import { TodoStateContext } from '../App';
const List = () => {
const todos = useContext(TodoStateContext); // <TodoStateContext.Provider value={todos}> 이렇게 단일 값으로 전달되고, 객체가 아니기 때문에 구조 분해 할당 const { todos } 으로 선언 안해도 된다.
...
}
TodoItem.jsx
import { TodoDispatchContext } from '../App';
const TodoItem = ({id, isDone, content, date}) => {
const { onUpdate, onDelete } = useContext(TodoDispatchContext);
...
}
그림출처: https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8/dashboard
'나의 주니어 개발 일기 > REACT' 카테고리의 다른 글
| REACT 에서 @ 사용으로 경로선언 깔끔하게 하기 (0) | 2025.12.23 |
|---|---|
| 페이지 라우팅 (0) | 2025.11.11 |
| [REACT] useMemo로 랜더링 최적화 하기 (6) | 2025.07.14 |
| [REACT] useReducer (0) | 2025.07.03 |
| [REACT] useEffect로 라이프사이클 관리하기 (1) | 2025.06.30 |