redux는 전역상태를 관리하기에 좋은 도구이다. 하지만 react에서 redux를 사용하려고 하면 꽤 많은 boilerplate code를 생성해야하는 단점이 있다. (물론 redux hook을 사용하면 많이 줄어든다.)
redux없이 react에서 제공하는 Context API를 이용하면 전역 상태를 redux와 유사하게 구현할 수 있다. 이와 더불어 useContext, useReducer 훅을 사용하면 구현이 더더욱 쉬워진다.
전역 상태를 활용해서 숫자를 increment, decrement, reset을 하는 예제를 만들어보자. (react 17 버전을 기준으로 작성하였다)
아래 코드는 CRA 환경에서 진행하였다.
1. store 디렉토리 생성 (src/store)
2. Action type 구현 (src/store/actionTypes.js)
아래와 같이 increment, decrement, reset 액션을 정의한다.
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const RESET = 'RESET';
3. Action creator 구현 (src/store/actionCreators.js)
각 액션 객체를 반환해주는 action creator를 구현한다.
import { INCREMENT, DECREMENT, RESET } from './actionTypes';
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
export const reset = () => ({ type: RESET });
4. Reducer 구현 (src/store/reducer.js)
각 액션에 따라 동작하는 reducer를 구현한다.
import { INCREMENT, DECREMENT, RESET } from './actionTypes';
export const reducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case INCREMENT:
return { ...state, number: state.number + 1 };
case DECREMENT:
return { ...state, number: state.number - 1 };
case RESET:
return { ...state, number: 0 };
default:
return state;
}
};
5. 전역 Context를 활용한 store 생성
초기 상태를 initialState에 정의한다. 증가, 감소, 리셋에 활용할 number의 값을 0으로 두었다.
createContext에 매개변수로 초기 상태를 전달하고 store 변수로 리턴되는 값을 받는다. store에는 {Provider, Consumer} 객체가 전달된다. Provider로 특정 컴포넌트를 감싸고 value 값으로 하위 컴포넌트로 전달할 값을 넣어주면, prop drilling없이도 모든 하위 컴포넌트들이 해당 값을 이용할 수 있다. 원래는 하위 컴포넌트에서 Consumer로 해당 컴포넌트를 감싸야하지만 여기서는 useContext를 활용하므로 Consumer를 사용하지 않아도 된다.
아래 코드를 보면 StoreProvider에서 useReducer에 위에서 정의한 reducer와 initialState을 전달하여 state과 dispatch를 반환받는다. 이 때 반환된 state은 우리가 사용할 전역 상태이고, dispatch는 action을 전달받아서 reducer를 실행해주는 메소드이다. state과 dispatch를 Provider의 value에 넣어주면 StoreProvider안에 포함된 모든 하위 컴포넌트에서 전역 상태에 접근할 수 있게 된다.
import { useReducer, createContext } from 'react';
import { reducer } from './reducer';
const initialState = {
number: 0,
};
const store = createContext(initialState);
const { Provider } = store;
const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return <Provider value={[state, dispatch]}>{children}</Provider>;
};
export { store, StoreProvider };
6. 전체 App을 StoreProvider로 감싸기 (src/index.js)
아래와 같이 App 컴포넌트를 위에서 구현한 StoreProvider로 감싸준다. 이제 App안의 모든 컴포넌트에서는 전역 Context에 접근해서 state과 dispatch를 이용할 수 있게 되었다.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { StoreProvider } from './store';
ReactDOM.render(
<React.StrictMode>
<StoreProvider>
<App />
</StoreProvider>
</React.StrictMode>,
document.getElementById('root')
);
7. 다른 컴포넌트에서 전역 상태를 사용하기 (src/App.js)
편의상 App.js에 버튼을 구현하였다. 아래 코드를 보면 useContext에 매개변수로 store를 전달하고 있다. 이 때 store는 src/store/index.js에서 export한 store이다. 이 값을 useContext에 전달하면 아까 우리가 Provider의 value에 주입한 값을 리턴해준다. 위에서 value에 store과 dispatch를 배열 형태로 전달했기 때문에 아래 코드와 같이 state과 dispatch를 리턴받을 수 있다.
여기서 state은 전역 상태이고 우리는 state.number의 값을 변경하고 다시 렌더링하는 코드를 작성하면 된다. 증가, 감소, 리셋 버튼을 클릭 시 해당 이벤트를 핸들링할 함수를 선언하고, 그 안에 dispatch 메소드를 이용해서 원하는 action을 전달하면 전역 state이 변화한다. 그리고 state 변화에 따라 App.js 컴포넌트를 새로운 number로 랜더링 된다.
import './App.css';
import { useContext } from 'react';
import { store } from './store';
import { decrement, increment, reset } from './store/actionCreators';
function App() {
const [state, dispatch] = useContext(store);
const handleIncrement = e => {
e.preventDefault();
dispatch(increment());
};
const handleDecrement = e => {
e.preventDefault();
dispatch(decrement());
};
const handleReset = e => {
e.preventDefault();
dispatch(reset());
};
return (
<div className="App">
<input type="number" value={state.number} readOnly />
<input type="button" onClick={handleIncrement} value="plus" />
<input type="button" onClick={handleDecrement} value="minus" />
<input type="button" onClick={handleReset} value="reset" />
</div>
);
}
export default App;
- 참고
codeburst.io/global-state-with-react-hooks-and-context-api-87019cc4f2cf
'프론트엔드 > react' 카테고리의 다른 글
[react] 리액트 배열의 key 값 (Each child in an array should have a unique “key” prop.) (0) | 2020.11.07 |
---|