useContext
개발하다 보면 props를 여러 번 넘길 때가 찾아오곤 합니다.
증조부모 → 조부모 → 부모 → 자식 → ...
이렇게 props를 계속 넘기다 보면 엄청난 노가다를 해야 될 수도 있고, 유지보수 측면, 가독성 등 안 좋은 점이 많습니다.
이를 개선하기 위해 전역적으로 데이터를 관리할 수 있는 Context를 사용하면 됩니다.
Context를 사용하면 증조부모에서 자식까지 한 번에 데이터를 전달할 수 있습니다.
(사용하기에 다른 hook보다 복잡할 수 있습니다.)
- createContext(initialValue)
- context객체 생성
- createContext호출 시, Provider와 Consumer 반환
- Context.Provider
- 생성한 context를 하위 컴포넌트에 전달
- useContext(Context);
- context의 상태 감시
- 상태값을 가져옴
예
useContext 검색해 보면 많이 나오는 마트 관련 소스입니다.
(누가 먼저 작성했는지는 모르겠는데.. 강의에서 봤던 거 같기도 하고..)
구조자체는 간단합니다.
최상단 컴포넌트 App에서 최하단 컴포넌트인 Emart3F까지 데이터를 전달하는데, useContext를 사용하지 않으면
App → Mart → Emart → Emart3F 까지 계속 props를 넘겨야 하는 일이 발생합니다.
이를 개선하여 App → Emart3F까지 한 번에 가보도록 수정해 봤습니다.
AppContext.js
import { createContext } from "react";
const initialItems = {
drink: [
{ name: "Powerade", price: "1800" },
{ name: "Coca Cola", price: "1100" }
],
fruits: [
{ name: "Apple", price: "3500" },
{ name: "Grape", price: "6000" }
]
};
export const AppContext = createContext(initialItems);
Context파일을 따로 분리하여 작성하는 게 가독성이나 관리 측면에서 좋아 보입니다.
객체 안에 drink와 fruites 상품을 초기값으로 지정해 주고, AppContext를 선언하여 다른 컴포넌트에서 사용할 수 있게 export 해줍니다.
App.js
// App.js
import React, { useState, useContext } from "react";
import { AppContext } from "./AppContext";
import Mart from "./Mart";
function App() {
const mart = useContext(AppContext);
const [items, setItem] = useState(mart);
const addDrink = (newDrink) => {
setItem({ ...items, drink: [...items.drink, newDrink] });
console.log(newDrink.name + "가 추가되었습니다.");
};
return (
<div style={{ position: "absolute", top: "5%", left: "5%" }}>
<AppContext.Provider value={{ items, addDrink }}>
<Mart />
</AppContext.Provider>
</div>
);
}
export default App;
createContext()를 사용해 AppContext를 선언해 주고, 태그 리턴해줄 때 <AppContext.Provider>로 감싸 주면 됩니다.
그리고 props로 넘길 것들을 정의해 주고 추가해 주면 됩니다. (해당 소스에서는 items, addDrink가 되겠다.)
Mart와 Emart 컴포넌트는 그냥 트리를 잡기 위한 컴포넌트로 사용했습니다.
Emart3F
// ./components/Emart3F.jsx
import React, { useContext } from "react";
import { AppContext } from "./AppContext";
function Emart3F() {
const newDrink = { name: "HOT6", price: "1500" };
const { items, addDrink } = useContext(AppContext);
return (
<div>
<h3>이마트 3층</h3>
<div>
음료수
{items.drink.map((item, index) => {
return (
<div key={index}>
{item.name} : {item.price}원
</div>
);
})}
<button onClick={() => addDrink(newDrink)}>음료 품목 추가</button>
</div>
<div>
과일
{items.fruits.map((item, index) => {
return (
<div key={index}>
{item.name} : {item.price}원
</div>
);
})}
</div>
</div>
);
}
export default Emart3F;
App.js에서 AppContext.Provider로 감싸줬기 때문에 Emart3F컴포넌트에서 AppContext를 받아서 사용할 수 있습니다.
useContext에 AppContext를 넣어 AppContext에 있는 items랑 addDrink를 구조분해할당하여 받을 수 있고,
받은 것들을 통해 화면에 그려주거나 버튼을 눌러 addDrink함수를 실행시켜 newDrink인 HOT6를 추가할 수 있게 구성되어 있습니다.
단점
ContextAPI를 사용하면 규모가 작을 때는 편리하게 개발할 수 있을 것입니다. 하지만, Context값이 변경될 때마다
useContext가 렌더링을 하게 되고, Context.Provider로 감싼 컴포넌트는 모두 리렌더링이 되는 것을 볼 수 있을 것입니다.
따라서 오히려 성능저하를 일으킬 수 있다는 단점이 있습니다.
(해결법은 다음 게시글에서...)
useContext를 사용해서 props 넘기는 데 있어 한층 더 간편하게 개발할 수 있을 것입니다.
간편하게 사용할 수 있는 만큼 성능 저하 측면을 고려하면서 사용해야 될 것이고..
'Front-End > React' 카테고리의 다른 글
React] Fragment <></> (0) | 2023.06.26 |
---|---|
React] Hook : useContext 단점 해결 (0) | 2023.06.26 |
React] Hook : useState 비동기 → 동기 하는 법 (0) | 2023.06.22 |
React] Hook : useReducer (0) | 2023.06.22 |
React] Hook : useCallback (0) | 2023.06.22 |