최근에 React를 이용한 과제를 하나 풀어본 적이 있고 지금은 개인적으로 포트폴리오 웹페이지를 작성하고 있는데 상태관리를 하는데에 있어서 Redux가 가장 일반적으로 사용되는 줄 알고 Redux를 이용해서 상태관리를 하고있었다.
그런데, React로 개발하고 있는 친구에게 'reducer, action, action type 등을 파일 하나에 정리했냐', '디렉토리 구조는 어떻게 했냐' 등을 물어봤는데 Redux를 안쓴다고 했다.
어... 그럼 뭐로 상태관리를 하냐 물어보니 Context API를 쓰는데? 라는 답변을 듣고 찾아봤다.
그래서 그 내용을 한번 정리해볼까 한다.
컴포넌트에서 데이터를 저장하고 관리하는 객체이다.
이 데이터는 컴포넌트가 렌더링될 때 화면에 표시되거나, 사용자의 상호작용에 따라 변경되는 값들을 의미한다.
import React, { useState } from 'react';
const ExampleComponent = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
위 코드는 간단한 예제인데 컴포넌트 내부에서 useState 라는 Hook을 이용해서 상태관리를 하는 예제이다.
이처럼 간단하게 사용할 수 있는 Hook이 있는 반면, 이것만으로는 조금 모자란 부분이 있다.
예를 들어, 자식 컴포넌트에 예제의 count 를 사용하고 싶다, 또 전혀 다른 컴포넌트에서도 이 count 를 사용해야한다. 그럴때 부모에게 데이터를 넘기고 그 부모의 부모에게, 그 부모의 자식에게 이런식의 데이터 바인딩은 너무 번거롭고 귀찮게 된다.
그래서 가장 주로 사용되는 상태관리 라이브러리 context API, Redux 이다. (context API 는 React에 내장된 상태관리 기능이다)
Context API 는 React 애플리케이션에서 전역적으로 상태를 관리하고 데이터를 전달하는 방법을 제공 해준다.
이를 통해 중간 컴포넌트를 거치지 않고도 컴포넌트 트리 전체에서 데이터를 공유할 수 있게 해준다.
예시를 살펴보자
import React, { createContext, useContext, useState } from 'react';
// NOTE(hajae): scroll의 높이 상태를 관리
const ScrollContext = createContext<number>(0);
export const useScrollContext = () => {
return useContext(ScrollContext);
};
type ScrollProviderProps = {
children: React.ReactNode;
};
export const ScrollProvider: React.FC<ScrollProviderProps> = ({ children }) => {
const [scrollPosition, setScrollPosition] = useState(0);
const handleScroll = () => {
const scrollHeight = window.scrollY; // scroll의 높이를 저장
setScrollPosition(scrollHeight);
};
React.useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<ScrollContext.Provider value={scrollPosition}>
{children}
</ScrollContext.Provider>
);
};
위 코드는 지금 공부하며 만들고 있는 포트폴리오에 사용된 코드이다.
위 코드를 살펴 보면,
// createContext로 Context 생성. 저장되는 데이터 타입은 number이며 초기값은 0 이다.
const ScrollContext = createContext<number>(0);
// 다른 컴포넌트에서 useScrollContext를 이용해 context를 사용 가능
export const useScrollContext = () => {
return useContext(ScrollContext);
};
createContext 로 Context를 생성하고 useContext 로 Context를 이용할 수 있다.
또 그 아래 ScrollProvider 과 같은 Provider이 존재하는데 이는 컴포넌트 트리 상단에 데이터를 제공하는 역할을 해준다.
위와 같이 작성 후,
// index.tsx
/*****생략*****/
<ScrollProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<MainComponent/>}></Route>
</Routes>
</BrowserRouter>
</ScrollProvider>
/*****생략*****/
이와 같이 앱의 최상위 컴포넌트에 위치시켜서 앱 전체에 걸쳐 공유할 데이터를 설정해야 한다.
이런식으로 작성 후,
...
const OtherComponent: React.FC = () => {
const scrollContext = useScrollContext();
...
이와 같이 다른 컴포넌트에서 scroll height의 데이터를 사용할 수 있다.
뭔가 적다보니 설명 없이 작성법만 적은것 같아서 정리를 해보자면,
Context API 는 전역적으로 상태를 관리하고 데이터를 전달하는 방법을 제공해주며 작성 순서는 아래와 같다.
1. context 를 생성한다
2. Provider 를 생성한다.
3. Provider 를 적용한다.
4. Component에서 사용한다.
Redux 는 간단하게 작성할 수 있지만 복잡할 수 있기때문에 일단 내가 알고 있는 간단한 부분만 설명하려한다.
Redux 는 상태 관리 라이브러리로서, 복잡한 상태를 효율적으로 관리할 수 있다.
아래는 예시이다.
import {createStore} from 'redux';
// NOTE(hajae): 액션 타입 정의
const SET_HOVERED_TAB = 'SET_HOVERED_TAB';
// NOTE(hajae): 액션 생성자
export const setHoveredTab = (tabName: string) => ({
type: SET_HOVERED_TAB,
payload: tabName,
});
const initialState = {
hoveredTab: '',
};
const reducer = (state = initialState, action: any) => {
switch (action.type) {
case SET_HOVERED_TAB:
return {
...state,
hoveredTab: action.payload,
};
default:
return state;
}
};
// NOTE(hajae): 스토어 생성
const store = createStore(reducer);
export default store;
위 코드는 hover되어 있는 tab의 name을 스토어에 저장하는 store의 코드이다.
이와 같이 작성 후, Context API 의 Provider과 같이 상위 컴포넌트에서 주입해줄 필요가 있다.
// index.tsx
/*****생략*****/
<Provider store={store}>
...
<Routes>
<Route path="/" element={<MainComponent/>}></Route>
</Routes>
...
</Provider>
/*****생략*****/
똑같이 사용하는 컴포넌트에서는
...
const OtherComponent: React.FC<OtherComponentProps> = (props: OtherComponentProps) => {
const hoveredTab = useSelector((state: any) => state.hoveredTab);
const dispatch = useDispatch();
// NOTE(hajae): hover한 tab 이름을 관리
const handleMouseEnter = (tabName: string) => {
dispatch(setHoveredTab(tabName));
};
const handleMouseLeave = () => {
dispatch(setHoveredTab(''));
};
...
...
<div
onMouseEnter={() => handleMouseEnter(tab.headerTabName)}
onMouseLeave={handleMouseLeave}>
...
이런식으로 다른 컴포넌트에서 사용한다.
사용측에서는 Dispatch(액션을 발생시켜 상태를 변경하는 메소드) 를 이용하여 작성해둔 액션 생성자를 불러준다.
또한, Subscribe 와 같이 상태가 변경될 때마다 실행되는 콜백 함수를 등록하는 메소드도 있다.
자세히 들어가면 내용이 많이 길어질 것 같으니 오늘은 조금 가볍게 이정도만 정리하려한다.
다음엔 Redux를 자세히 정리하거나, Hook에 대해 정리해볼까한다.
조금 정리도 없이 막 가져다가 쓴 감이 없지않아 있지만, 내용이 틀린부분이 있으면 언제든지 피드백 부탁드립니다. 감사합니다.
좋은 정보 얻어갑니다, 감사합니다.