개요.
- 리액트에서 데이터의 흐름은 부모 컴포넌트에서 자식에게 인자(props)로 전달하는 맥락이 원칙이다.
- 이러한 기본적인 맥락 외에 새로운 맥락을 생성할 수 있게 해주는 방법이 Context API이다.
- Context Api는 props drilling을 회피하기 위한 것이 본래 목적이다.
- 즉, 의존성을 함수형 컴포넌트에 인자가 아닌 함수 내부에서의 호출로 주입 받는다.
1. 새 컨텍스트 생성하기
import { createContext } from 'react';
export const ThemeContext = createContext(defaultValue);
-컨텍스트 이름은 대문자로 시작한다.
-컨텍스트 자체는 어떤 정보도 포함하지 않는다.
-defaultValue는 이 context에 접근할 수 없을 때 반환되는 값이다.
2. 컨텍스트 props 내려주기
function App() {
// ...
return (
<ThemeContext.Provider value={theme}>
<Todo />
</ThemeContext.Provider>
);
}
// 위는 아래와 같이 생각 할 수 있다. '컴포넌트 합성(composition)'
function App() {
// ...
return (
<ThemeContext.Provider children={<Todo />} value={theme} />
);
}
-컨텍스트 객체 안에는 Provider라는 컴포넌트(ThemeContext.Provider)가 들어있다.
-이 컴포넌트에 공유하고자 하는 value를 props로 전달하고, 이 value에 접근할 다른 컴포넌트들을 합성시켜준다.
3. 컨텍스트 접근하기
function Todo() {
return (
<Form>
<Button />
</Form>
);
}
// ...
function Button() {
const theme = useContext(ThemeContext);
return (
<button style={{backgroundColor: theme}}>제출하기</button>
);
}
-useContext(ThemeContext)는 현재 컴포넌트에서 가장 가까운 ThemeContext.Provider에 전달된 value를 반환한다.
-useContext(ThemeContext)를 호출하는 컴포넌트(Button)는 반드시 자신 혹은 부모 컴포넌트(Todo)가 ThemeContext.Provider 컴포넌트에 wrapping되어야 한다.
- 컴포넌트 최상단에서 useContext를 호출하게 되면 이 컴포넌트는 useContext에 인자로 전달된 context를 subscribe한다.
4. 컨텍스트 value 변경하기
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
<Todo />
</ThemeContext.Provider>
);
}
-프로바이더에 전달한 value가 바뀌면 이 컨텍스트를 구독 중인 (useContext(ThemeContext)를 호출하여 해당 value를 참조하는) 모든 컴포넌트는 re-render된다.
-useContext가 반환한 값은 사실 부모 컴포넌트(App)에서 내려준 props(value:theme)와 동일하다.
-value로 state를 전달하면 setState가 실행될 때마다 프로바이더를 리턴하는 함수(App)가 재실행되면서 value 값도 바뀌게 되고, 리액트는 이를 참조하는 모든 컴포넌트를 리렌더링한다.
-> "The returned value is always up-to-date. React automatically re-renders components that read some context if it changes."
5. Context Provider Container 컴포넌트와 useContext Hook 만들기
-ThemeProvider 컴포넌트와 같이 context value를 처리하는 로직을 담당하는 container 컴포넌트를 만든다.
-Context를 여러 컴포넌트에서 사용된다면 커스텀 Hook으로 만들어 관리한다.
import { createContext, useContext } from 'react';
const ThemeContext = createContext(defaultValue);
const useThemeContext = () => {
const value = useContext(ThemeContext);
if(value === defaultValue) throw new Error('useThemeContext should be used within ThemeProvider')
return value;
}
function ThemeProvider ({ children }) {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
function App() {
return (
<ThemeProvider>
<Todo />
</ThemeProvider>
);
}
function Todo() {
return (
<Form>
<Button />
</Form>
);
}
function Button() {
const theme = useThemeContext();
return (
<button style={{backgroundColor: theme}}>제출하기</button>
);
}
-*참고: https://codesandbox.io/s/context-advanced-etst2w?file=/src/App.js
6. Context 렌더링 최적화
-리액트 메모이제이션 훅 사용하기 + 역할에 따라 여러 Context 분리하기
import { createContext, useContext, useMemo, useRef, useState } from 'react';
const TodosValueContext = createContext();
const TodosActionsContext = createContext();
function TodosProvider({ children }) {
const idRef = useRef(3);
const [todos, setTodos] = useState([
{
id: 1,
text: '밥먹기',
done: true
},
{
id: 2,
text: '잠자기',
done: false
}
]);
const actions = useMemo(
() => ({
add(text) {
const id = idRef.current;
idRef.current += 1;
setTodos((prev) => [
...prev,
{
id,
text,
done: false
}
]);
},
toggle(id) {
setTodos((prev) =>
prev.map((item) =>
item.id === id
? {
...item,
done: !item.done
}
: item
)
);
},
remove(id) {
setTodos((prev) => prev.filter((item) => item.id !== id));
}
}),
[]
);
return (
<TodosActionsContext.Provider value={actions}>
<TodosValueContext.Provider value={todos}>
{children}
</TodosValueContext.Provider>
</TodosActionsContext.Provider>
);
}
function useTodosValue() {
const value = useContext(TodosValueContext);
if (value === undefined) {
throw new Error('useTodosValue should be used within TodosProvider');
}
return value;
}
function useTodosActions() {
const value = useContext(TodosActionsContext);
if (value === undefined) {
throw new Error('useTodosActions should be used within TodosProvider');
}
return value;
}
7. useReducer를 대체하여 update 함수 만들기
-위 예시의 actions 객체처럼 업데이트 함수들이 들어있는 객체를 Provider 컴포넌트에서 바로 선언하여 context로 공유한다.
-다음과 같이 사용할 수 있다.
// 할 일 항목을 추가할 때
const { add } = useTodosActions();
const handleSubmit = () => {
add(text);
}
// 각 항목을 보여줄 때
const { toggle, remove } = useTodosActions();
const handleToggle = () => {
toggle(id);
};
const handleRemove = () => {
remove(id);
};
* useReducer를 활용하여 상태 업데이트를 할 수 있게 구현하고 dispatch를 별도의 context로 넣어주는 방식도 많이 사용한다.
-> https://kentcdodds.com/blog/how-to-use-react-context-effectively
-useReducer의 장점: 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다.
( *useReducer 사용방법: https://velog.io/@velopert/react-hooks )
*참고: https://velog.io/@velopert/react-context-tutorial
'React' 카테고리의 다른 글
[React] useReducer (0) | 2023.11.09 |
---|---|
[React] Portal (0) | 2023.07.04 |
[React] Recoil (0) | 2023.03.21 |
[React] React-Query (0) | 2022.11.14 |
[React] Redux Toolkit & Middleware (0) | 2022.10.31 |