빵 입니다.
리액트의 Context ➡️ Context API 본문
리액트는 props를 관리하는데, props는 컴포넌트에 전달하는 데이터로 컴포넌트 구성을 하게 해주고 부모-자식 컴포넌트 간의 통신을 연결해 주는 역할을 해준다.
문제는 여러 개의 컴포넌트를 거쳐 데이터를 전달할 때 일어난다.
state를 여러 컴포넌트를 통해 props로 전달하는 경우
- App ➡️ Header ➡️ Navigation
- App에서 Login 정보를 Header에 props로 내려준다.
- Header에서 직접 사용하지 않고, Header 안에 있는 Navigation에 Login 정보를 props로 내려준다.
- Navigation에서 직접 사용한다.
=> Header는 그냥 데이터를 전달만 할 뿐
이렇게 단순하게 props를 내려줄 수 있다.
그러나 DOM 트리가 복잡할 경우
LoginForm의 Login 데이터를 Shop이나 Cart에서 사용하고 싶을 경우, 부모인 Auth를 거쳐서 가야한다.
- LoginForm ➡️ Auth ➡️ Shop/Cart
Product일 경우엔 부모도 거치고, 부모의 형제도 거치고, 그 형제의 자식도 거쳐야 한다.
- Product ➡️ Products ➡️ Shop ➡️ Auth/Cart
모든 컴포넌트가 직접 연결되어 있지 않다.
Product 컴포넌트의 Add to Cart 함수에 부모 컴포넌트에서 만든 함수를 전달 해야 한다면, 부모 컴포넌트들은 Add to Cart 함수가 필요하지 않더라도 Product 컴포넌트에 데이터를 전달하기 위해서만 Add to Cart 함수가 필요하다.
이런걸 "프롭 체인"이라고 한다.
📌 프롭체인
데이터를 컴포넌트를 통해 다른 컴포넌트에 전달하는 것
그런데 트리 구조가 깊어지고 넓어진 경우 프롭체인을 사용하면 복잡하니까
프롭체인을 사용하는 대신 필요한 데이터를 실제로 사용할 컴포넌트에서만 받는다면 더 좋지 않을까?
부모를 통해 데이터 전달하지 않고 다이렉트로 필요한 컴포넌트에서 사용한다면?
(어차피 부모 컴포넌트에선 관리도, 사용도 하지도 않음)
이런 복잡한 내용들을 해결하기 위해 나온 것이 "리액트 컨텍스트"이다.
📌 리액트 Context
Context는 리액트 애플리케이션에서 전역 상태를 관리하기 위한 기능을 제공하는 도구이다.
컴포넌트 간에 데이터를 공유할 때 사용되며, 프로퍼티 전달이나 중간 컴포넌트를 거치지 않고 전역적으로 상태를 관리할 수 있게 해준다.
리액트에서는 기본적으로 컴포넌트 간의 데이터 전달은 부모에서 자식으로 프로퍼티를 통해 이루어지는데 앱이 복잡해지고 여러 컴포넌트 간에 데이터를 전달해야 하는 경우, 프로퍼티를 여러 단계로 거쳐야 하는 불편함이 발생할 수 있다. 이때 Context를 사용하면 전역적으로 상태를 관리하여 이러한 문제를 해결할 수 있다. (프롭 체인 만들지 않고 필요한 컴포넌트에서 직접 접근해서 사용 가능하다.)
Context를 사용하면 상위 컴포넌트에서 하위 컴포넌트로 데이터를 명시적으로 전달하지 않아도 된다. 대신, Context를 통해 전역적으로 데이터를 공유하고 필요한 컴포넌트에서 해당 데이터를 사용할 수 있다.
🔥앱의 여러 개의 전역 state에 대해 여러 개의 컨텍스트를 가질 수 있다.
🔥더 큰 state에 대해 하나의 컨텍스트만 사용할 수도 있다.
📌 Context 생성하기
createContext를 사용하여 컨텍스트를 생성한다.
// 이렇게 하거나
import React, { createContext } from 'react';
const MyContext = createContext();
// 이렇게 하거나
import React from 'react';
const MyContext = React.createContext();
React.createContext를 호출하면 컨텍스트 객체가 반환된다.
컨텍스트 객체는 Provider와 Consumer 컴포넌트를 속성으로 가지고 있으며, 속성 접근자(.)로 컴포넌트를 포함하는 컨텍스트 객체 속성에 접근한다.
📌 Context 사용하기 - 공급하기
- Provider 컴포넌트는 컨텍스트의 값을 설정하고 하위 컴포넌트에게 해당 값을 전달하는 역할을 한다.
- Provider 컴포넌트로 감싸진 하위 컴포넌트들은 해당 컨텍스트의 값을 사용할 수 있게 된다.
- value prop을 통해 전달되는 값은 하위 컴포넌트에서 Consumer를 통해 접근할 수 있다.
➡️ 컨텍스트 값을 받아오기 위해서는 해당 컨텍스트를 리스닝하는 Consumer 컴포넌트를 사용해야 한다.
<MyContext.Provider value={/* some value */}>
{/* 하위 컴포넌트들 */}
</MyContext.Provider>
value 속성에 상태 값을 할당한다.
value에 불변하는 속성만을 넣으면 해당 속성이 변경되어도 리렌더링이 되지 않을 수 있으므로, useState값을 할당해서 연결해줘야 상태에 따라 값이 변한다.
// 🌀불변하는 속성 ❌
value={{ isLoggedIn: false }}
// 🌀상태 관리 변수 이용 ⭕️
const [isLoggedIn, setIsLoggedIn] = useState(false);
value={{
isLoggedIn: isLoggedIn
}}
📌 Context 사용하기 - 소비하기
Context를 소비하는 방법은 두가지(Consumer 컴포넌트, useContext)
1. Consumer 컴포넌트 사용
- Consumer 컴포넌트는 컨텍스트의 값을 소비하고, 해당 값을 이용하여 렌더링을 수행하는 역할을 한다.
- Consumer 컴포넌트는 함수를 자식(children)으로 가지며, Provider 컴포넌트에서 value props에 전달된 값을 매개변수(value)로 컨텍스트의 데이터가 전달된다.
<MyContext.Consumer>
{value => (
<p>Value from context: {value}</p>
)}
</MyContext.Consumer>
2. useContext() 훅 사용
- useContext 훅을 사용하여 컨텍스트를 소비한다.
- useContext는 특정 컨텍스트 객체를 받아와서 해당 컨텍스트의 현재 값을 반환한다.
- Hooks를 활용하므로 함수형 컴포넌트에서 더 간결하게 코드를 작성할 수 있다.
- 여러 컨텍스트를 동시에 사용할 때도 더 편리하다.
import React, { useContext } from 'react';
import MyContext from './MyContext';
const MyComponent = () => {
const value = useContext(MyContext);
return (
<p>Value from context: {value}</p>
);
};
export default MyComponent;
📌 컨텍스트를 동적으로 설정하기
컨텍스트를 사용하여 컴포넌트 외부에서 함수를 하위 컴포넌트로 전달할 수 있다.
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
onLogout: logoutHandler,
}}
>
{/* 자식 컴포넌트들 */}
</AuthContext.Provider>
로그인 상태를 나타내는 변수 isLoggedIn와 로그아웃을 처리하는 함수 logoutHandler을 하위 컴포넌트로 전달한다.
📌 컨텍스트 제한
🌀props를 컨텍스트로 대체할 순 없다.
리액트 컨텍스트를 사용하여 전역 상태를 관리할 때, 해당 상태가 여러 컴포넌트에 영향을 미칠 수 있지만, 컨텍스트를 사용해서 컴포넌트의 구성을 완전히 대체하거나 교체할 수는 없다.
컨텍스트는 주로 상태의 공유와 업데이트를 위해 사용되며, 일반적으로 컴포넌트 간에 데이터를 전달하거나 공유할 때 사용한다. 그러나 컴포넌트의 구성은 주로 프롭(props)를 통해 이루어지며, 컨텍스트로 대체하기엔 어렵다.
// Button.js
import React, { useContext } from 'react';
import Button from './Button';
import AuthContext from './AuthContext';
const SomeComponent = () => {
const authCtx = useContext(AuthContext);
return (
<div>
{/* 이 버튼은 재사용이 어려워진다. */}
<Button onClick={authCtx.onLogout} />
</div>
);
};
재사용가능한 Button 컴포넌트에 컨텍스트를 통해 특정 함수를 전달하면, 해당 버튼은 해당 컨텍스트와 관련된 특정 동작만 수행할 수 있게 된다.
재사용할 순 있지만 특정 동작(ex. 로그아웃)만 하는 버튼이 되어 버린다.
각 버튼들이 하는 일이 다르니까 컨텍스트에 제한을 두면 안된다.
이런 재사용 컴포넌트에는 props를 사용하는것이 낫다.
🌀변경이 잦은 경우에는, 리액트 컨텍스트는 적합하지 않다.
= 컨텍스트는 정적인 데이터나 변경이 빈번하지 않은 데이터를 다루는 데에 적합하다.
리액트 컨텍스트는 컨텍스트의 값이 변경될 때 해당 컨텍스트를 사용하는 모든 하위 컴포넌트가 리렌더링되기 때문에, 값이 빈번하게 변경되는 상황에서는 매번 리렌더링이 발생하여 성능 저하를 초래할 수 있다.
// 1초 마다 화면이 업데이트되는 상태
import React, { createContext, useState, useEffect } from 'react';
const MyContext = createContext();
const MyProvider = ({ children }) => {
const [dynamicState, setDynamicState] = useState('Initial State');
useEffect(() => {
const interval = setInterval(() => {
setDynamicState(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(interval);
}, []);
const contextValue = {
dynamicState,
};
return (
<MyContext.Provider value={contextValue}>
{children}
</MyContext.Provider>
);
};
export { MyProvider, MyContext };
❓ 앱 전체에 걸쳐 또는 컴포넌트 전체에 걸쳐 state가 자주 변경되는 경우엔?
❓ props도 Context도 부적합하다면?
❗️리덕스를 사용하자❗️
'프론트엔드 > React' 카테고리의 다른 글
forwardRef()와 useImperativeHandle() (0) | 2024.01.03 |
---|---|
Hooks의 규칙 (0) | 2023.12.14 |
리액트 주요 콘셉트 (0) | 2023.12.13 |
useState() vs useReducer() (0) | 2023.12.13 |
useReducer() (0) | 2023.12.13 |