useContext
useContext는 컴포넌트에서 context를 읽고 구독할 수 있게 해주는 React Hook입니다.
기본구문
const value = useContext(SomeContext)
매개변수
SomeContext : 이전에 createContext로 생성한 context입니다. context 자체는 정보를 보유하지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 종류를 나타낼 뿐입니다.
반환값
호출한 컴포넌트에서 트리상 위에 있는 가장 가까운 SomeContext.Provider에 전달된 value입니다. 이러한 provider가 없는 경우 반환되는 값은 해당 context에 대해 createContext에 전달한 defaultValue가 됩니다. 반환된 값은 항상 최신 값입니다. React는 context가 변경되면 context를 읽는 컴포넌트를 자동으로 리렌더링합니다.
React Context의 필요성
일반적으로 부모컴포넌트에서 자식 컴포넌트에게 데이터를 전달해줄때 props로 전달해준다. 하지만 큰 단점이 있는데 컴포넌트가 깊어질수록 해당 컴포넌트가 어느 컴포넌트에서 왔는지 파악하기 어려워진다. 이것을 props-drilling이라고 한다.
prop drilling의 문제점
- 깊이가 너무 깊어지면 이 prop이 어떤 컴포넌트로부터 왔는지 파악이 어려워진다.
- 어떤 컴포넌트에서 오류가 발생할 경우 추적이 힘들다.
Props 사용이 적절할 때❓
리스트 컴포넌트에서 아이템 컴포넌트로 상태를 전달하는 경우. 이 경우는 이번 프로젝트를 하면서 많이 생소했다. props drilling으로 만든 코드를 context 로 변경하는 과정에서 리스트 컴포넌트에는 도저히 context를 사용할 수 없을 것 같아서였다. map을 돌린 애들로 아이템을 만드는데 여기서 context를 어떻게 사용할까 했는데 오늘 강의 때 단순한 데이터 전달은 Props 사용이 적절한 때라고 알려주셔서 이해할 수 있었다.
export default function TodoList({ isDone }) {
const { todos, setTodos } = useContext(TodoContext);
const filteredTodos = todos.filter((todo) => todo.isDone === isDone);
return (
<div>
<h2>{isDone ? "Done" : "Working..."}</h2>
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} todo={todo} setTodos={setTodos} />
))}
</div>
);
}
export default function TodoListWrapper() {
return (
<>
<TodoList isDone={false} />
<TodoList isDone={true} />
</>
);
}
Context API
아래 그림처럼 useContext hook을 통해 우리는 쉽게 전역 데이터를 관리할 수 있다. Context Provider 의 모든 children 컴포넌트들에게 context value를 공유할 수 있고, 전역상태관리를 하게 되면 props 를 이용한 상태 공유를 하지 않아도 된다.
- createContext : context를 생성
- useContext : context를 구독하고 해당 context의 현재 값을 읽기
- Provider : context를 하위 컴포넌트에게 전달
createContext
Context Provider 의 모든 children 컴포넌트들에게 context value를 공유할 수 있다.
const TodoContext = createContext(null);
Provider
context를 하위 컴포넌트에게 전달한다.
function App () {
const [todos, setTodos] = useState([]);
return (
<TodoContext.Provider value={{todos, setTodos}}>
<TodoForm />
<TodoListWrapper />
</TodoContext.Provider>
);
}
예시
// src/context/TodoContextProvider.jsx
import React, { createContext, useState } from 'react';
// 함수 컴포넌트 외부에서 Context 생성
export const TodoContext = createContext(null);
const TodoContextProvider = ({ children }) => {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text, isDone: false }]);
};
return (
<TodoContext.Provider value={{ todos, addTodo }}>
{children}
</TodoContext.Provider>
);
};
export default TodosContextProvider;
useContext
context를 구독하고 해당 context의 현재 값을 읽어올 수 있다. useContext() 괄호 안은 리턴 + 구독을 뜻한다. 아래 코드는 todoContext를 구독하겠다는 말과 같다.
function TodoForm() {
const { setTodos } = useContext(TodoContext);
return (
...
)
}
실습을 하던 중에 디테일 페이지에 데이터를 받아야하는데 부모 컴포넌트와 종속성이 없어 데이터를 받아오지 못하는 상황이 있었다. 라우터 간 데이터 전달은 context api로 할 수 있는데 props-drilling으로 작업하고 있어서 어찌해야할 방법을 몰랐다. 그런데 설마 했던 방법이 맞았다..! 라우터 컴포넌트에서 상태를 전달하는 것..! 너무나도 생소해서 여긴 아니겠지 하는 생각으로 갈피를 잡지 못했었다.
// 여러 페이지 컴포넌트에 동일한 상태 공유가 필요할 때
function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/detail/id" element={<Detail />} />
</Routes>
</BrowserRouter>
)
}
Route.jsx에다 상태를 만들어주고 props로 내려주면 되는것.. 방법을 못찾아서 useLocation으로 데이터를 받아왔었는데 props-drilling의 불편함을 어느정도 알 수 있게해준 오류였다.
function Router() {
const [todos, setTodos] = useState([]);
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/detail/id" element={<Detail todos={todos}/>} />
</Routes>
</BrowserRouter>
)
}
'React' 카테고리의 다른 글
[리액트] memoization, useMemo, useCallback (0) | 2024.05.29 |
---|---|
[리액트] 내가 이해하려고 적는 Redux 라이브러리 (1) | 2024.05.28 |
[리액트] Localstorage 수정하기 (0) | 2024.05.24 |
[리액트] Localstorage 저장 및 삭제하기 (0) | 2024.05.23 |
[리액트] Localstorage 데이터 활용하기 (0) | 2024.05.22 |