Redux
: 전역상태 라이브러리
필요성
어떤 컴포넌트에서 생성한 state를 다른 컴포넌트로 보내고자할 때 props를 사용함
하지만 prop drilling이 일어나면서 그 state가 필요없는 컴포넌트도 props를 전달받고, 전달해야 함
또한 자식 컴포넌트에서 부모 컴포넌트로 값을 보낼 수 없다
Redux를 사용하면 자식에서 만든 state를 부모에서 사용할 수도 있고, 불필요한 전달이 필요없어진다
Global state 와 Local state
1) Local state (지역상태)
- 컴포넌트에서 useState를 이용해 생성한 state
2) Global state (전역상태)
- 컴포넌트에서 생성되지 않음!
- 중앙화된 특별한 곳에서 생성된 state들
- 컴포넌트가 어디 위치해있던 상관없이 state를 불러와 사용할 수 있음
- 또한 이런 값들을 관리하는 것을 전역 상태 관리라고 함
context API 사용시 제한사항
1) 성능 최적화
- context는 provider 하위의 모든 컴포넌트를 리렌더링
- 상태가 변경될 때마다 관련된 모든 컴포넌트가 불필요한 리렌더링이 발생하는 것을 막을 수 없음
- 반면 리덕스는 상태 변경과 관련된 컴포넌트만 선택적으로 업데이트할 수 있음
2) 상태 로직의 중앙화와 일관성
- 리덕스는 상태를 하나의 저장소(store)에 저장
- 이로인해 상태 로직이 중앙에서 관리되어 더 일관성 있고 예측 가능한 상태변경이 가능해짐
- 모든 상태변경 로직이 리듀서(reducers)에 의해 처리되기 때문에 디버깅과 테스팅이 용이함
3) 강력한 미들웨어와 개발도구
- 다양한 미들웨어를 지원하여 비동기 작업, 로깅, 상태 변경에 대한 추가 처리 등 복잡한 기능을 구현할 수 있음
- 또한 Redux DevTools같은 강력한 개발도구를 통해 상태 변화를 시각적으로 모니터링하고 이전 상태로 롤백하는 등의 기능을 제공
설치
yarn add redux react-redux
폴더구조 설정
src > redux > config, modules 두 폴더 생성
config > configStore.js 파일 생성 -> 중앙 저장소 store를 설정할 것
configStore.js
* reducer: state 관리를 위한 로직들, configStore에서 combineReducers로 모아둘 것
* createStore는 더이상 권장되지 않음 -> 추후 redux toolkit 다루면서 수정할 것
import { combineReducers, createStore } from "redux";
// 1) root reducer 만들기
const rootReducer = combineReducers({}); // 추후 modules에 들어갈 reducer들을 추가할 예정
// 2) store 생성
const store = createStore(rootReducer);
// 3) 만든 store를 내보내기
export default store;
main.jsx
- react-redux의 provider로 App을 감싼다
- Provider는 configStore에서 내보낸 store를 props로 받는다
- 그럼 App 의 모든 컴포넌트들은 그 저장소를 사용할 수 있다!
createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);
모듈 만들기
- 예시로 counter.js 를 제작
- reducer라는 함수를 제작해야 함, 이 함수는 state를 반환
- reducer 함수는 두개의 인자를 매개변수로 받음 (state, action)
- state: state 초기값
- action: type이라는 Key를 가지는 객체 (지금은 default만 지정, 나중에 type에따라 분기)
const initialState = { number: 0 };
// reducer == 함수
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default counter;
만들어준 리듀서를 configStore.js의 combineReducer에 전달해줌
const rootReducer = combineReducers({
counter: counter,
});
// 단축 가능
const rootReducer = combineReducers({
counter
});
key: value 형태로 import 한 리듀서를 넣어주면 되는데, key value가 같으니 단축해줌
컴포넌트에서 사용해보기
const App = () => {
const counterReducer = useSelector(state => state.counter);
console.log("state: ", counterReducer);
}

설정해둔 state를 확인할 수 있음
dispatch action 객체
- store의 변화는 dispatch()에 의해서만 발생하고, dispatch는 action 객체를 리듀서에 전달한다!
- 리듀서는 전달받은 action의 type에 따라 해당하는 처리를 진행하고, 그래서 store에 변화가 일어남
redux 흐름
- View 에서 액션이 일어난다.
- dispatch 에서 action이 일어나게 된다.
- action에 의한 reducer 함수가 실행되기 전에 middleware가 작동한다.
- middleware 에서 명령내린 일을 수행하고 난뒤, reducer 함수를 실행한다.
- reducer 의 실행결과 store에 새로운 값을 저장한다.
- store의 state에 subscribe 하고 있던 UI에 변경된 값을 준다.
state 수정 기능 만들어보기
state +1 기능을 만들어보자~
1) useDispatch()를 이용해 dispatch 객체를 만들어줌
2) onClick 이벤트로 dispatch를 실행하게 함
3) dispatch를 실행할 때 객체(action)에 type을 지정해 매개변수로 보내줌
4) reducer에 타입에 해당하는 동작을 설정해줌
import { useDispatch, useSelector } from "react-redux";
import counter from "./redux/modules/counter";
const App = () => {
const counterReducer = useSelector((state) => state.counter);
const dispatch = useDispatch();
return (
<>
<h1>{counterReducer.number}</h1>
<button
onClick={() => {
dispatch({ type: "PLUS_ONE" });
}}
>
+1
</button>
<button
onClick={() => {
dispatch({ type: "MINUS_ONE" });
}}
>
-1
</button>
</>
);
};
export default App;
const initialState = { number: 0 };
// reducer == 함수
const counter = (state = initialState, action) => {
switch (action.type) {
case "PLUS_ONE":
return { number: state.number + 1 };
case "MINUS_ONE":
return { number: state.number - 1 };
default:
return state;
}
};
export default counter;
payLoad, ducks
action.type을 상수로 관리하기
- type은 지금 문자열로 쓰여있음
- 만약 이를 변경하고 싶다면 reducer의 switch문, 사용하고있는 컴포넌트들에서 하나하나 수정을 해줘야 함
- 따라서 별도의 상수로 관리해보자!
1) counter.js (reducer 모듈) 에서 필요한 상수와 그 상수를 반환하는 함수 만들기 + switch문에서도 문자열이 아닌 상수로 case 나누기
const PLUS_ONE = "PLUS_ONE";
export const plusOne = () => {
return { type: PLUS_ONE };
};
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_ONE:
return { number: state.number + 1 };
case MINUS_ONE:
return { number: state.number - 1 };
default:
return state;
}
};
2) app.jsx (리듀서를 사용하는 컴포넌트) 에서 객체와 그 안의 문자열을 직접 작성하는 것이 아니라 상수를 반환하는 함수를 사용
<button
onClick={() => {
dispatch(plusOne());
}}
>
+1
</button>
<button
onClick={() => {
dispatch(minusOne());
}}
>
-1
</button>
- 이렇게 action을 생성해주는 동작을 action creator라고 함
payload
- type과 함께 action 객체를 구성하는 요소
- 예를들어 위 예시들처럼 +1, -1이 아니라 +n, -n 을 사용자가 직접 지정할 수 있도록 한다면 이 때 리듀서로 전달되는 n을 payload라고 한다!
const initialState = { number: 0 };
const ADD_NUMBER = "ADD_NUMBER";
export const addNumber = (payload) => {
return { type: ADD_NUMBER, payload: payload };
};
const REMOVE_NUMBER = "REMOVE_NUMBER";
export const removeNumber = (payload) => {
return { type: REMOVE_NUMBER, payload: payload };
};
// reducer == 함수
const counter = (state = initialState, action) => {
switch (action.type) {
case ADD_NUMBER:
return { number: state.number + action.payload };
case REMOVE_NUMBER:
return { number: state.number - action.payload };
default:
return state;
}
};
export default counter;
const App = () => {
const [count, setCount] = useState(0);
const counterReducer = useSelector((state) => state.counter);
const dispatch = useDispatch();
return (
<>
<h1>{counterReducer.number}</h1>
<input
type="number"
value={count}
onChange={(e) => {
setCount(+e.target.value);
}}
></input>
<button
onClick={() => {
dispatch(addNumber(count));
}}
>
더하기
</button>
<button
onClick={() => {
dispatch(removeNumber(count));
}}
>
빼기
</button>
</>
);
};
export default App;
Ducks
Ducks 패턴
- 리덕스 앱을 구성할 때 사용하는 방법론 중 하나
- 일반적으로 분산돼있던 액션 타입, 액션 생성자, 리듀서를 하나의 파일로 구성하는 방식
- Redux 관련 코드의 관리를 보다 간결하고 모듈화하여 관리하도록 도움
Ducks 패턴으로 작성하기
위 내용이 이미 Ducks 패턴으로 작성되고 있었다!
1) Reducer 함수를 export default
2) Action creator 함수들을 export
3) Action type은 app/reducer/ACTION_TYPE 형태로 작성
'React' 카테고리의 다른 글
[React] 컴포넌트에 styled-components 로 스타일 적용하기 (0) | 2024.08.23 |
---|---|
RTK - Redux Toolkit (0) | 2024.08.21 |
[React + Supabase] 설치, 셋팅, 간단한 사용법 (0) | 2024.08.20 |
[React] react-router-dom (0) | 2024.08.20 |
[React] Effect가 필요하지 않을 수도 있습니다 (0) | 2024.08.18 |