React Context
리액트의 하나의 컴포넌트에서 데이터를 생성하거나 업데이트하거나 다른 컴포넌트와 데이터를 공유해서 사용하는 여러 방법이 있습니다.
- state 와 props를 사용해서 컴포넌트 간에 데이터를 전달
- React Context API를 사용해서 컴포넌트 간에 데이터를 전달
- Redux, MobX, Recoil 등의 상태 관리 라이브러리를 사용
useContext 란?
- React Context는 Component 트리 전체에 props를 전달하지 않고도 Component 데이터를 제공하는 방법입니다.
- Context는 전역 상태를 관리하기 위한 간단한 방법입니다.
Context 문법
React.createContext
함수를 사용하여Context
를 생성합니다.Context.Provider
컴포넌트를 만나면,value
prop을 통해Context
의 값을 제공합니다.useContext
컴포넌트를 사용하여Context
의 값을 사용합니다.
Context 사용하기
React.createContext
함수를 사용하여 Context를 생성합니다.
const MyContext = React.createContext(defaultValue);
Context.Provider
컴포넌트를 사용하여 Context를 제공합니다.
<MyContext.Provider value={/* 상태 값 */}>
{/* 여기에 위치한 컴포넌트들은 이 Context의 값을 사용할 수 있음 */}
</MyContext.Provider>
- Context 사용
일반 함수 컴포넌트에서 Context 값을 읽을 때는 useContext
hook을 사용합니다.
const value = useContext(MyContext);
Context.Consumer
컴포넌트는 클래스 컴포넌트에서 Context 값을 읽을 때 주로 사용
<MyContext.Consumer>
{value => /* 상태 값에 따라 UI를 렌더링 */}
</MyContext.Consumer>
Context를 사용한 Theme 예제
const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
// createContext 함수를 사용하여 Context 생성
const ThemeContext = React.createContext(themes.light);
function App() {
return (
// context value를 공유할 컴포넌트를 감싸기
// 공유하기 위한 value를 설정
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// context value를 사용하기 위해 useContext hook 사용
const theme = useContext(ThemeContext);
return <button style={{ background: theme.background, color: theme.foreground }}>I am styled by theme context!</button>;
}
export default App;
Context를 이용한 앱 만들기
- 주문 페이지
여러 컴포넌트의 가격과 옵션을 공유하여 총액을 계산하여 보여주는 페이지를 만들어보겠습니다.
import React, { useState, useContext } from 'react';
// context 생성
const OrderContext = React.createContext();
function App() {
const [order, setOrder] = useState({
price: 0,
option: 'none',
});
return (
// context 제공
// context value를 공유할 컴포넌트를 감싸기
<OrderContext.Provider value={{ order, setOrder }}>
<Order />
<Total />
</OrderContext.Provider>
);
}
function Order() {
// context value를 사용하기 위해 useContext hook 사용
const { order, setOrder } = useContext(OrderContext);
const handleChange = e => {
let price = 0; // 기본 가격을 설정
switch (e.target.value) {
case 'sugar':
price = 1000;
break;
case 'milk':
price = 1500;
break;
case 'none':
default:
price = 0;
break;
}
setOrder({ ...order, option: e.target.value, price: price });
};
return (
<div>
<h1>Order</h1>
<select
value={order.option}
onChange={handleChange}
>
<option value="none">None</option>
<option value="sugar">Sugar</option>
<option value="milk">Milk</option>
</select>
</div>
);
}
function Total() {
const { order } = useContext(OrderContext);
return (
<div>
<h1>Total</h1>
<p>Price: {order.price}원</p>
<p>Option: {order.option}</p>
</div>
);
}
export default App;
- 할 일 관리 앱 Context API 사용하기
// src/context/TodoContext.js
import React, { createContext, useReducer, useContext } from 'react';
// 초기 상태
const initialState = [
{
id: 1,
isDone: false,
task: '고양이 밥주기',
createdDate: new Date().getTime(),
},
{
id: 2,
isDone: false,
task: '감자 캐기',
createdDate: new Date().getTime(),
},
{
id: 3,
isDone: false,
task: '고양이 놀아주기',
createdDate: new Date().getTime(),
},
];
// reducer 함수
function reducer(state, action) {
switch (action.type) {
case 'ADD':
return [action.payload, ...state];
case 'UPDATE':
return state.map(it => (it.id === action.payload ? { ...it, isDone: !it.isDone } : it));
case 'DELETE':
return state.filter(it => it.id !== action.payload);
default:
return state;
}
}
// Context 객체 생성
const TodoStateContext = createContext(); // 상태 값 조회
const TodoDispatchContext = createContext(); // dispatch 함수 조회
// 커스텀 훅 생성
// 상태 값 조회
export function useTodoState() {
return useContext(TodoStateContext); // Context 객체의 현재 값을 반환
}
export function useTodoDispatch() {
return useContext(TodoDispatchContext); // Context 객체의 현재 값을 반환
}
// Provider 컴포넌트 생성
export function TodoProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<TodoStateContext.Provider value={state}>
<TodoDispatchContext.Provider value={dispatch}>{children}</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
);
}
// src/components/App.js
import React from 'react';
import TodoHd from './TodoHd';
import TodoEditor from './TodoEditor';
import TodoList from './TodoList';
import { TodoProvider } from '../context/TodoContext';
function App() {
return (
<TodoProvider>
<div>
<TodoHd />
<TodoEditor />
<TodoList />
</div>
</TodoProvider>
);
}
export default App;
// src/components/TodoList.js
import React, { useState } from 'react';
import TodoItem from './TodoItem';
import { useTodoState, useTodoDispatch } from '../context/TodoContext';
export default function TodoList() {
const todo = useTodoState();
const dispatch = useTodoDispatch();
const [search, setSearch] = useState('');
const onChangeSearch = e => {
setSearch(e.target.value);
};
const filteredTodo = () => {
return todo.filter(item => item.task.toLowerCase().includes(search.toLowerCase()));
};
return (
<div>
<h2>할 일 목록 📃</h2>
<input
value={search}
onChange={onChangeSearch}
placeholder="검색어를 입력하세요"
/>
<ul>
{filteredTodo().map(item => (
<TodoItem
key={item.id}
{...item}
/>
))}
</ul>
</div>
);
}
// src/components/TodoEditor.js
import React, { useState } from 'react';
import { useTodoDispatch } from '../context/TodoContext';
export default function TodoEditor() {
const dispatch = useTodoDispatch();
const [task, setTask] = useState('');
const onChangeTask = e => {
setTask(e.target.value);
};
const onSubmit = () => {
if (!task) {
return;
}
dispatch({ type: 'ADD', payload: { id: Date.now(), isDone: false, task, createdDate: new Date().getTime() } });
setTask('');
};
const onKeyDown = e => {
if (e.key === 'Enter') {
onSubmit();
}
};
return (
<div className="TodoEditor">
<h2>새로운 Todo 작성하기 ✏ </h2>
<div>
<input
value={task}
onChange={onChangeTask}
onKeyDown={onKeyDown}
placeholder="할 일을 추가로 입력해주세요."
/>
<button onClick={onSubmit}>추가</button>
</div>
</div>
);
}
// src/components/TodoItem.js
import React from 'react';
import { useTodoDispatch } from '../context/TodoContext';
export default function TodoItem({ id, isDone, task, createdDate }) {
const dispatch = useTodoDispatch();
const onUpdate = () => {
dispatch({ type: 'UPDATE', payload: id });
};
const onDelete = () => {
dispatch({ type: 'DELETE', payload: id });
};
return (
<div>
<li key={id}>
<input
type="checkbox"
checked={isDone}
onChange={onUpdate}
/>
<span style={{ textDecoration: isDone ? 'line-through' : 'none' }}>{task}</span>
<span>{new Date(createdDate).toLocaleDateString()}</span>
<button onClick={onDelete}>삭제</button>
</li>
</div>
);
}
'Front > React' 카테고리의 다른 글
리액트 ES6 문법 정리 - 기본편 (0) | 2024.03.29 |
---|---|
리액트(React.js) 커리큘럼 (0) | 2024.03.24 |
useReducer - React 배우기 (0) | 2024.03.19 |
최적화 - React 배우기 (0) | 2024.03.18 |
React를 이용한 할 일 관리 앱 만들기 (0) | 2024.03.14 |