[React] useContext 사용하기
JavaScriptReact2021.04.16
useContext?
- context는 리액트 버전에 따라 정말 많이 변경된 API임
한 때는 쓰지 말라고 공식문서에서 말하기까지..
요즘은 React.createContext와 useContext가 나오면서 활발히 사용되고 있음 - Redux를 useContext로 대체하려는 움직임도 보임
useContext 왜 사용하려고??
- 기존에 컴포넌트 간에 데이터를 전달하려면 props를 이용해야 했음
- 만약 A, B, C, D, E 컴포넌트가 있을 때 A에서 E로 데이터를 내려보내주려면
중간 B, C, D 컴포넌트를 거쳐야 함. 엄청난 비효율
시나리오 및 예시 코드
시나리오
- 어떠한 Container에서 데이터가 수정되거나 추가 / 삭제 등 될 때
다른 Container도 변경되어야 함.
이 때 상태가 변경됨을 알릴 flag(상태)가 필요한데 (여기선isDataUpdate)
너무 해당 상태 값이 너무 깊숙히 있음.
이 때 prop-drilling을 하지 않고 가져오고 싶을 때!
예시 코드
먼저 상태가 저장될 파일을 생성 (Store 느낌의 파일을 생성,
util에다 넣어도 좋고 약간 컴포넌트 느낌이라 다른 곳에 놓아도 될 듯)
// ./src/lib/utility/TodoStore.js
// TodoContext, TodoStore 정의
import React, { createContext, useState, useMemo } from 'react';
export const TodoContext = createContext({
/*
- Context의 기본값 지정
(Provider의 value안에 해당 속성들이 없으면 요놈들이 기본값으로 됨!)
굳이 해주지 않아도 되지만 TodoContext를 import 할 때 유용!
*/
isDataUpdate: null,
setIsDataUpdate: () => {},
});
const TodoStore = ({ children }) => {
// Provider의 value에 들어갈 state 생성
const [isDataUpdate, setIsDataUpdate] = useState(false);
// value 객체는 객체이므로 리렌더링의 원인이 됨, useMemo로 캐싱
const value = useMemo(() => ({ isDataUpdate, setIsDataUpdate }), [
isDataUpdate,
setIsDataUpdate,
]);
// 이 예제에선 TodoContext 컴포넌트 사용 시,
// Parent가 아닌 GrandParent 느낌의 최상단에 위치함
// children으로 다른 컴포넌트들을 가져옴
return (
<TodoContext.Provider value={value}>
{children}
</TodoContext.Provider>
);
};
export default TodoStore;
리액트 앱의 최상단 App.js 수정
// ./src/App.js
// 여기선 TodoStore를 가져옴
import React from 'react';
import './App.css';
import TopbarContainer from './containers/topbar/TopbarContainer';
import MainContainer from './containers/main/MainContainer';
import TodoStore from "./lib/utility/TodoStore";
const App = () => {
return (
// TodoStore를 요로코롬 감싸줌
<TodoStore>
<TopbarContainer />
<MainContainer />
</TodoStore>
);
};
어떠한 Container (CardContainer) 에서 데이터가 수정되거나 추가 / 삭제 등 될 때
TodoContext에 정의해준 setIsDataUpdate 사용하여 IsDataUpdate 업데이트
(여기선 서버로 삭제 요청)
// ./src/containers/main/column/card/CardContainer.js
import React, { useState, useEffect, useRef, useContext } from 'react';
import axios from "axios";
import Card from "../../../../components/main/column/card/Card";
import PopupModal from "../../../../components/common/PopupModal";
import { TodoContext } from "../../../../lib/utility/TodoStore";
const CardContainer = ({ title, body, index, columnId, cardId, previousCardId, isZanSang, setColumnData}) => {
const { setIsDataUpdate } = useContext(TodoContext);
/* --- 생략 --- */
// Popup Events
// 팝업에서 확인버튼 -> 카드 삭제 (서버로 delete 요청)
const onClickPopupConfirm = () => {
setColumnData((data)=>{
const newData = [...data]
newData[columnId-1].cards = newData[columnId-1].cards.filter((_, i) => i !== index)
return newData;
});
axios.delete(`api/columns/${columnId}/cards/${cardId}`);
setIsDataUpdate(true); // TodoContext의 Provider value의 IsDataUpdate를 업데이트
};
/* --- 생략 --- */
return (
<>
<Card data={data} flags={flags} onEvents={onEventsCard} />
<PopupModal />
</>
);
};
export default CardContainer;
변경되어야 하는 다른 Container인 TopbarContainer 수정
// ./src/containers/topbar/TopbarContainer.js
import React, { useEffect, useState, useContext } from 'react';
import { TodoContext } from '../../lib/utility/TodoStore';
import { calcPastTime, fetchData } from '../../lib/utility/util';
import TopbarTemplate from '../../components/topbar/TopbarTemplate';
const TopbarContainer = () => {
// useContext를 사용하여 TodoContext를 가져옴 & 비구조화 할당을 통해
// TodoContext.Provider의 value 값을 가져와줌
const { isDataUpdate, setIsDataUpdate } = useContext(TodoContext);
/* --- 생략 --- */
// 요로코롬 사용해줌! (isDataUpdate 상태 체크 후 -> 다시 서버에 get 요청!)
useEffect(() => {
if (isDataUpdate) {
getActivityData();
setIsDataUpdate(false);
}
}, [isDataUpdate, setIsDataUpdate]);
/* --- 생략 --- */
return <TopbarTemplate />
};
export default TopbarContainer;
마지막으로.. useContext 사용 시 주의사항
- useContext를 쓸 때 주의할 사항은, Provider에 제공한 value가 달라지면
useContext를 쓰고 있는 모든 컴포넌트가 리렌더링 된다는 것.. - 지금 value 안에는
isDataUpdate,setIsDataUpdate들어있는데
앞으로 개수가 더 늘어날 가능성이 높음.
그 중 하나라도 바뀌면 객체로 묶여있으므로 전체가 리렌더링 되어버림..
따라서 잘못 쓰면 엄청난 렉을 유발할 수 있음!!
참고 자료
