저는 주로 전역 상태관리를 recoil 로
작업하고 있었습니다.
그러다 문득 redux 기능에 대해서 궁금해졌고
정리해 보았습니다.
사용하는 이유
state 종속성 탈피
우리는 useState 를 사용 할 경우 컴포넌트 내부에 state 를 만들고, 함수로 state 를 바꿉니다.그렇기 때문에 state 는 컴포넌트에 종속되는 것은 당연한 결과 입니다. redux 는 컴포넌트에 종속되지 않고, 상태관리를 컴포넌트 바깥에서 합니다.프로젝트 루트레벨에서 store 라는 곳에 state 를 저장하고, 모든 컴포넌트는 store 에 구독을 하면서 state 와 그 state 를 바꾸는 함수를 전달 받게 되죠.함수를 바꿈으로 state 가 변경되면 해당 state 를 바라보고 있는 컴포넌트는 모두 리렌더링 됩니다.
기본 용어
액션
상태에 어떠한 변화가 필요하면 액션이란 것이 발생합니다.액션 객체는 type 필드를 반드시 가지고 있어야 합니다. 이 값을 액션의 이름이라고 생각하면 됩니다.
액션 생성 함수
액션 객체를 만들어 주는 함수입니다.
리듀서
변화를 일으키는 함수입니다.액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아옵니다.그리고 두 값을 참고하여 새로운 상태를 만들어 변환해 줍니다.
스토어
프로젝트에 리덕스를 적용하기 위해 스토어를 만듭니다.스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 중요한 내장 함수를 지닙니다.한 개의 프로젝트는 단 하나의 스토어만 가질 수 있다는 것을 명심하세요.
디스패치
디스패치는 액션을 발생시키는 것이라고 이해하면 됩니다.스토어의 내장 함수 중 하나로써, 이 함수가 호출되면 스토어는 우리가 만들어둔 리듀서 함수를 실행시켜서 새로운 상태를 만들어 줍니다.
사용해보기
들어가기에 앞서 react-redux 는 redux 에 기반하여 만들어진 기능이기 때문에 redux 를 모르신다면 공부하시고 오세요!!
설치
npm i redux react-redux
처음에는 actions 를 정의해 주어야 합니다.
// src/redux/actions.js
export const INCREASE = 'INCRESE';
export const DECREASE = 'DECRESE';
export const SET_USER = 'SET_USER';
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
export const setUser = (user) => ({ type: SET_USER, payload: user });
다음으로 리듀서 함수를 만들어 주어야 합니다.
// src/redux/countReducer.js
import { INCREASE, DECREASE } from './actions';
const initialState = {
count: 0,
};
export function countReducer(state = initialState, action) {
switch (action.type) {
case INCREASE:
return {
...state,
count: state.count + 1,
};
case DECREASE:
return {
...state,
count: state.count - 1,
};
default:
return state;
}
}
// src/redux/userReducer.js
import { SET_USER } from './actions';
const initialstate = {
user: null,
};
export function userReducer(state = initialstate, action) {
switch (action.type) {
case SET_USER:
return {
...state,
user: action.payload,
};
default:
return state;
}
}
이렇게 리듀서까지 만들어 주었다면 스토어에 넣어주어야 합니다. 여러개의 리듀서를 만들어서 스토어에 넣어주고 싶다면
redux-toolkit 에서 지원해 주는 combineReducers 함수를 사용하시면 됩니다.
설치는 npm i @reduxjs/toolkit 로 설치해 주시고 사용하시면 됩니다.
// src/redux/rootReducer.js
import { combineReducers } from '@reduxjs/toolkit';
import { countReducer } from './countReducer';
import { userReducer } from './userReducer';
const rootReducer = combineReducers({
count: countReducer,
user: userReducer,
});
export default rootReducer;
이렇게 만들어준 묶은 리듀서를 store 에 넣어주어야 겠죠??
// src/redux/store.js
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
export const store = configureStore({
reducer: rootReducer,
});
// src/App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './redux/store';
import './App.css';
import Button from './component/Button';
function App() {
return (
<Provider store={store}>
<Button>Click me</Button>
</Provider>
);
}
export default App;
여기까지가 기본 설정입니다.
최상단 부모 컴포넌트에 연결을 해 주어야 그 밑에 자식 컴포넌트들이 사용할 수 있습니다.
이제 자식 컴포넌트에서 props 로 넘겨받지 않고 저장소인 store 에서 꺼내서 사용해 보겠습니다.
// src/component/Button.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increase, decrease, setUser } from '../redux/actions';
const Button = ({ children }) => {
const count = useSelector((state) => state.count.count);
const user = useSelector((state) => state.user.user);
const dispatch = useDispatch();
console.log(user);
return (
<div>
<p>{children}</p>
<p>Count: {count}</p>
<button onClick={() => dispatch(increase())}>Increase</button>
<button onClick={() => dispatch(decrease())}>Decrease</button>
<button onClick={() => dispatch(setUser('Chan'))}>
change User Name
</button>
{user && <p>{user}</p>}
</div>
);
};
export default Button;
특정 값을 꺼내고 싶다면 useSelector 을 사용하시고, 특정 값을 변경하고 싶다면 위에 용어 설명에서 보았듯이
우리의 reducer 함수를 사용할 수 있는 dispatch 를 꺼내면 되는데 꺼낼 때는 useDispatch 함수를 이용하시면 됩니다.
변수로 만든 dispatch 함수 안에 저희가 정의한 액션 생성 함수를 dispatch 함수 안에 넣어서 실행시키면 됩니다.
여기까지 기본적인 react-redux 사용법 입니다.
useSelector 최적화
const { number, diff } = useSelector(state => ({
number: state.counter.number,
diff: state.counter.diff
}));
만약 이렇게 두개의 값을 동시에 사용하는 컴포넌트가 있다고 가정해 봅시다.
그리고 이 두개의 값을 변경하는 특정 함수도 있다고 가정해 보면 number 값만 변경되도 diff 값이 포함된 컴포넌트도 리렌더링 되는 것을
보실 수 있을 것입니다.
사실상 useSelector Hook을 통해 매번 렌더링 될 때마다 새로운 객체 { number, diff }를 만드는 것이기 때문에 상태가 바뀌었는지 바뀌지 않았는지 확인을 할 수 없어서 낭비 렌더링이 이루어지고 있는 것이죠.
이를 최적화 하기 위해선 두가지 방법이 있습니다.
첫번째 방법은 간단하게 useSelector 을 두번 하는 것입니다.
const number = useSelector(state => state.counter.number);
const diff = useSelector(state => state.counter.diff);
이렇게 하면 해당 값들 하나라도 바뀌었을 때에만 컴포넌트가 리렌더링 됩니다.
두번째는, react-redux 의 shallowEqual 함수를 useSelector 의 두번째 인자로 전달해주는 것입니다.
const { number, diff } = useSelector(
state => ({
number: state.counter.number,
diff: state.counter.diff
}),
shallowEqual
);
React-Redux는 상태 관리를 체계적으로 할 수 있게 도와주는 강력한 도구라고 생각합니다.
특히 대규모 애플리케이션에서 상태 추적과 관리를 용이하다고도 생각합니다.
그러나 recoil 을 사용하는 입장으로써 작은 프로젝트에서는 오히려 복잡성을 증가시킬 수 있으며,
러닝 커브가있을 수 있다고 생각했습니다.
프로젝트의 규모와 필요에 따라 적절히 선택하여 사용하는 것이 중요하다고 느껴봅니다.
'React' 카테고리의 다른 글
React 에서 files 다루기 (0) | 2024.07.29 |
---|---|
Axios (0) | 2024.07.28 |
useCallback (0) | 2024.05.03 |
useMemo (0) | 2024.05.03 |
useEffect (0) | 2024.05.01 |