MJ.Story
Redux 본문
- 리액트에서는 조건이나 동작에 따라 변화할 수 있는 상태를 state라고 부르고, 해당 상태를 하위 컴포넌트에 props의 형태로 전달하여 사용하곤 한다.

- 다만 위의 그림처럼 상위 컴포넌트에서 하위 컴포넌트로 상태값을 전달하기 위해서는 두 컴포넌트 사이에 있는 모든 컴포넌트를 거쳐 전달해야 하는 단점이 있었는데, 이러다 보니 코드가 복잡해짐과 동시에 상태값을 전달하는 역할의 컴포넌트에서는 사용하지 않을 불필요한 속성값을 받게 된다는 단점이 있었다.
// 하위 컴포넌트 D로 state 값을 전달하는 순서
// without redux
컴포넌트 A state
컴포넌트 B 하위 전달
컴포넌트 C 하위 전달
컴포넌트 D pros
// with redux
[중앙 Store에서 state 관리]
컴포넌트 A
컴포넌트 B
컴포넌트 C
컴포넌트 D store.getState()
- 그래서 등장한 것이 Flux 아키텍처와 이를 구현한 리덕스(Redux)로, 리덕스를 사용하면 상태값을 중앙 스토어에서 관리할 수 있어 복잡한 컴포넌트 계층 구조에서도 상태를 효과적으로 관리할 수 있다.
리덕스란,
- 우리가 위에서 말한 “중앙 state 관리소”를 사용할 수 있게 도와주는 패키지(라이브러리) 입니다. “중앙 state 관리소" 를 통해서 State를 관리한다는 아이디어는 굉장히 좋으나, 우리가 그것을 직접 구현하기는 아직 어려우니까요. 패키지(라이브러리)의 도움을 받아 그것을 구현해보고자 합니다.
- 프론트엔드 개발자들은 “리덕스”를 전역 상태관리 라이브러리 라고 많이 표현합니다. 전역 상태, 즉 Global State를 의미하고 그것을 관리하게 도와주는 라이브러리 (패키지) 이기 때문입니다.
리덕스의 구성
- 리덕스는 크게 다음 요소들로 구성된다.
- 상태값을 저장하는 저장소: 스토어
- 스토어는 모든 상태값을 저장하며 상태값을 조작할 리듀서 함수를 인자로 받는다.
- 상태를 조작하는 함수 : 리듀서 함수
- 리듀서(Reducer) 함수는 초기 상태값과 액션(action)을 인자로 받아 액션에 따라 조작할 상태를 지정한다.
- 액션을 전달하는 함수 : 디스패처 함수
- 디스패처(Dispatcher) 함수는 액션값과 상태에 관한 데이터를 리듀서 함수에 전달한다.
Global state와 Local state
- 우리는 앞으로 State를 Global state와 Local state라는 것을 따로 구분지어서 표현할 것 입니다.
- Local state (지역상태)란?
- 컴포넌트에서 useState를 이용해서 생성한 state 입니다. 좁은 범위 안에서 생성된 State라고 생각하시면 됩니다.
- Global state (전역상태)란?
- Global state는 컴포넌트에서 생성되지 않습니다. 중앙화 된 특별한 곳에서 State들이 생성됩니다.
- 좀 더 쉽게 얘기해서 “중앙 state관리소” 라고 생각하면 됩니다.
- Local state (지역상태)란?

- 중앙 State관리소에서 State를 생성하고, 만약 어떤 컴포넌트에서 State가 필요하다면 컴포넌트가 어디에 위치하고 있든 상관없이 State를 불러와서 사용 할 수 있게 됩니다. 이렇게 특정 컴포넌트에 종속되어 있는 것이 아니라 “중앙 state관리소” 에서 생성된 State를 Global state라고 합니다. 그리고 이러한 값들을 관리하는 것을 전역 상태 관리 라고 합니다.
정리
- 리덕스는 전역상태 관리 라이브러리다.
- 리덕스는 useState를 통해 상태를 관리했을 때 발생하는 불편함을 일부 해소시켜준다.
- 리덕스는 중앙 State 관리소를 가지고 있으며, 모든 State는 이곳에서 생성된다.
- useState로 생성한 State는 Local State이고, 리덕스에서 생성한 State는 Global State이다.
알아보면 좋은 키워드
- propsDrilling란?
리덕스 설정
- 리덕스 설치
yarn add redux react-redux
아래와 같은 의미
yarn add redux
yarn add react-redux
- 폴더와 파일의 역할
- redux : 리덕스와 관련된 코드를 모두 모아 놓을 폴더입니다.
- config : 리덕스 설정과 관련된 파일들을 놓을 폴더 입니다.
- configStore : “중앙 state관리소”인 Strore를 만드는 설정 코드들이 있는 파일 입니다.
- modules : 우리가 만들 State들의 그룹이라고 생각하면 됩니다 예를 들어 투두리스트를 만든다고 한다면 투두리스트에 필요한 state들이 모두 모여있을 todos.js를 생성하게 되는데요, 이 todos.js 파일이 곧 하나의 모듈이 됩니다.
- src/configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
/*
1. createStore()
리덕스의 가장 핵심이 되는 스토어를 만드는 메소드(함수) 입니다.
리덕스는 단일 스토어로 모든 상태 트리를 관리한다고 설명해 드렸죠?
리덕스를 사용할 시 creatorStore를 호출할 일은 한 번밖에 없을 거예요.
*/
/*
2. combineReducers()
리덕스는 action —> dispatch —> reducer 순으로 동작한다고 말씀드렸죠?
이때 애플리케이션이 복잡해지게 되면 reducer 부분을 여러 개로 나눠야 하는 경우가 발생합니다.
combineReducers은 여러 개의 독립적인 reducer의 반환 값을 하나의 상태 객체로 만들어줍니다.
*/
const rootReducer = combineReducers({});
const store = createStore(rootReducer);
export default store;
- index.js
// 원래부터 있던 코드
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
// 우리가 추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣어줍니다.
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals>
reportWebVitals();
정리
- 리액트에서 리덕스를 사용하려면, redux, react-redux가 필요하다.
- 설정코드는 지금 장장 이해 할 필요가 없다.
- “중앙 State 관리소”를 Store (스토어)라고 부른다.
- 모듈이란, State들이 그룹이다.
모듈이란?
모듈이란, State의 그룹이라고 했습니다. 우리의 첫 모듈은 카운터 프로그램에 필요한 State들이 모여있는 모듈이 될 것 입니다.
1 .모듈 만들기
- modules폴더에 counter.js 파일을 생성한다.
// src/modules/counter.js
// 초기 상태값
const initialState = {
number: 0,
};
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
// 모듈파일에서는 리듀서를 export default 한다.
export default counter;
- 모듈의 구성요소 살펴보기
- initialState === 초기 상태값
이것이 initialState 입니다. 단어 그대로 초기 상태값 입니다. 즉 어떤 State의 초기값을 정해주는 것 입니다.// 초기 상태값 const initialState = { number: 0, };
위 코드에서 만든 State의 초기값은 { } (객체)이고, 그 안에 number 라는 변수에 초기값 0을 할당해준 것입니다. 초기값은 꼭 객체가 아니어도 됩니다. 배열이 되어도 되고, 그냥 원시데이터가 돼도 됩니다. 그리고 객체에서도 여러개의 변수를 넣어줄 수 있습니다.const [number, setNumber] = useState(0) // <- 여기 - // 초기값이 0 const initialState = 0; // 초기값이 0이 있는 배열 const initialState = [0]; // 초기값이 number = 0, name = '석구'인 객체 const initialState = { number: 0, name: '석구' };
- 우리가 useState를 사용했을 때 괄호 안에 초기값을 지정해주던 것과 같은 이치입니다.
2. Reducer === 변화를 일으키는 함수
- 아래 코드를 리듀서라고 합니다.
- 리듀서란, 변화를 일으키는 함수 입니다.
- 리듀서는 함수다. 이것만 기억해봅시다.
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
- 우리가 useState()를 사용할 때, number라는 값을 바꾸고 싶으면 setNumber를 사용했습니다.
- 아래 코드 처럼 number값을 변경할 수 있었습니다.
// 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
- 리덕스에서는 리듀서가 이 역할을 합니다.
- 우리가 “리듀서야 number에 +1를 해줘” 라고 명령하며느 리듀서는 number에 +1을 더해줍니다.
- 그래서 변화를 일으키는 함수라고 표현한 것 입니다.
// src/redux/modules/counter.js
// counter 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default counter; // 여기
- 그리고 리듀서의 인자에 보면 (state = initialState, action)이라고 되어 있습니다.
- 우리는 리듀서 인자 첫번째 자리에서는 state를, 두번째 자리에서는 action이라는 것을 꺼내서 사용할 수 있습니다. 이것이 무엇인지는 지금 당장 몰라도 됩니다. 다만 state = initialState처럼 state에 initialState를 할당해줘야 하는 것만 기억하자.
- 카운터 모듈을 스토오에 연결하기
- 우리는 지금까지 모듈파일에서 초기 상태값과 리듀서를 작성했습니다. 이제 우리가 만든 모듈을 스토어에 연결 시켜야 합니다. 아직까진 모듈과 스토어가 각각 따로 분리되어 있는 상태이기 때문에 우리가 만든 State를 스토어에서 꺼낼 수 없습니다.
- consfigStore.js로 이동해서 아래 코드를 추가해주세요
// src/redux/modules/config/configStore.js
// 원래 있던 코드
import { createStore } from "redux";
import { combineReducers } from "redux";
// 새롭게 추가한 부분
import counter from "../modules/counter";
const rootReducer = combineReducers({
counter: counter, // <-- 새롭게 추가한 부분
});
const store = createStore(rootReducer);
export default store;
- 위와 같이 코드를 추가하면, 스토어와 모듈이 연결됩니다. 이렇게 스토어와 모듈을 연결시키는 코드는 우리가 모듈을 추가할 때마다 똑같이 진행해주면 됩니다.
스토어와 모듈 연결 확인하기
- useSelector = 스토어 조회
- 우리가 생성한 모듈을 스토어에 잘 연결했는지 확인하는 방법은 컴포넌트에서 스토어를 직접 조회하면 됩니다. 컴포넌트에서 리덕스 스토어를 조회하고자 할때는 useSelector라는 ‘react-redux’의 훅을 사용해야 합니다.
- useSelector의 사용법
// 1. store에서 꺼낸 값을 할당 할 변수를 선언합니다.
const number =
// 2. useSelector()를 변수에 할당해줍니다.
const number = useSelector()
// 3. useSelector의 인자에 화살표 함수를 넣어줍니다.
const number = useSelector( ()=>{} )
// 4. 화살표 함수의 인자에서 값을 꺼내 return 합니다.
// 우리가 useSelector를 처음 사용해보는 것이니, state가 어떤 것인지 콘솔로 확인해볼까요?
const number = useSelector((state) => {
console.log(state)
return state
});
App.js컴포넌트로 이동해서 기존에 있던 코드를 지우고 아래코드 입력
// src/App.js
import React from "react";
import { useSelector } from "react-redux"; // import 해주세요.
const App = () => {
const counterStore = useSelector((state) => state); // 추가해주세요.
console.log(counterStore); // 스토어를 조회해볼까요?
return <div></div>;
}
export default App;
- 우리는 컴포넌트에서 스토어를 조회할 때 react-redux에서 제공하는 useSelector라는 훅을 사용합니다(너무중요)!!!
- 브라우저를 켜고, 콘솔을 보면 아래 이미지처럼 객체가 보이고, 그 안에 counter 라는 값이 있는 것을 볼 수 있습니다. 우리가 만든 counter라는 모듈의 state가 보이는 것을 알 수 있습니다. 이렇게 화살표 함수에서 꺼낸 state라는 인자는 현재 프로젝트에 존재하는 모든 리덕스 모듈의 state 인 것 입니다.
- 이제 우리는 어떤 컴포넌트에서도 접근 할 수 있는 스토어를 가지게 되었습니다. 만약 우리가 컴포넌트에서 number라는 값을 사용하고자 한다면 아래 코드처럼 꺼내서 사용하면 됩니다.
const number = useSelector(state => state.counter.number); // 0
- 정리
- 보통 모듈은 기능의 이름을 따서 파일을 생성한다.
- 모듈의 구성요소로는 initialState, Reducer가 있다.
- 모듈을 만들면, 스토어에 연결을 해야만 한다. 그리고 연결은 configStore.js에서 하낟.
- 컴포넌트에서 Stroe를 조회할 때는 useSelector를 사용해야 한다.
- useSelector((state) ⇒ state) 에서 state는 모든 모듈의 state를 조회할 수 있는 값이다.
리덕스의 흐름 도식화

- View 에서 액션이 일어난다.
- dispatch 에서 action이 일어나게 된다.
- action에 의한 reducer함수가 실행되기 전에 middleware가 작동한다.
- middleware에서 명령내린 일을 수행하고 난뒤, reducer함수를 실행한다. (3, 4번은 아직 몰라도 된다)
- reducer의 실행결과 store에 새로운 값을 저장한다.
- store의 state에 subscribe하고 있던 UI에 변경된 값을 준다
- counter.js 모듈의 state 수정 기능 만들기 (+1 기능 구현해보기)
- // 예시 코드 // local state const [number, setNumber] = useState(0) // click handler const onClickHandler = () => { setNumber(number + 1) }
- 리덕스에서 값의 수정은 리듀서에서 일아난다.
- 리듀서에게 보낼 number를 +1 하라는 “명령”을 만든다.
- 명령을 보낸다
- 리듀서에서 명령을 받아 number + 1을 한다.
- 리듀서에게 보낼 “명령” 만들기
- 우선 우리는 리듀서에게 number에 +1을 하라고 명령을 보내야겠죠. 명령을 보내기 전에 ‘명령'을 만들어야 합니다. 리덕스에서는 그 명령을 Action 이라고 합니다. 즉, 리듀서에게 내가 어떤 Action을 하길 원한다. 라고 표현하는 것이죠. 행동을 코드로 나타내면 객체 로 만듭니다. 그래서 이것을 액션 객체 라고 합니다.
- 액션 객체는 반드시 type이라는 key를 가져야 합니다. 왜냐하면 우리가 이 액션 객체를 리듀서에게 보냈을 때 리듀서는 객체 안에서 type이라는 key를 보기 때문입니다.
// 예시 코드 //number에 +1 을 하는 액션 객체 { type : "PLUS_ONE" };- 앞으로 우리는 리덕스 모듈에 있는 state을 변경하기 위해서는 그에 해당하는 액션 객체를 모두 만들어줘야 합니다.
- “명령”(액션 객체) 보내기
- 이제 명령을 만들었으니, 우리는 리듀서에게 명령을 보내야 합니다. 조금 더 정확하게 표현하게 위해서 지금부터는 “명령"이 아닌 액션객체라고 표현하겠습니다.
- 액션객체를 보내기 리듀서로 보내기위해서는 새로운 훅을 사용해야 합니다. 그 훅은 useDispatch라는 훅 입니다. react-redux에서 import 해서 사용할 수 있으며, 우리가 만든 액션 객체를 리듀서로 보내주는 역할을 하는 훅입니다.
- useDispatch라는 훅을 사용하기 위해서는 컴포넌트 안에서 아래와 같이 먼저 코드를 작성해서 dispatch라는 변수를 생성해줘야 합니다. 이렇게 생성한 dispatch는 함수 라는 점을 기억하세요! 그래서 우리는 dispatch를 사용할 때 () 를 붙여서 함수를 실행하게 됩니다.
// src/App.js import React from "react"; import { useDispatch } from "react-redux"; // import 해주세요. const App = () => { const dispatch = useDispatch(); // dispatch 생성 return ( <div> <button // 이벤트 핸들러 추가 onClick={() => { // 마우스를 클릭했을 때 dispatch가 실행되고, ()안에 있는 액션객체가 리듀서로 전달된다. dispatch({ type: "PLUS_ONE" }); }} > + 1 </button> </div> ); }; export default App;- 자, 이렇게 우리는 디스패치를 이용해서 액션객체를 리듀서로 보낼 수 있습니다. 만약에 어떤 컴포넌트가 렌더링 됐을 때 액션객체를 리듀서로 보내고 싶다면 어떻게 해야 할까요? 한번 고민해보시길 바랍니다.
- 액션 객체 받기
- 우리가 (3)에서 리듀서로 액션을 보내긴 보냈지만, 무언가 눈으로 보이는게 없으니 이게 지금 잘 된건가? 궁금하지 않으신가요? 우리가 액션객체를 리듀서로 보냈으니, 리듀서에서 액션객체가 잘 왔는지 확인해보겠습니다.
- 아마 현재 counter.js 모듈의 코드가 이런 상태 일 것 입니다. 코드를 수정해볼 것인데요. 우리가 App.js에서 보낸 액션객체를 받을 수 있도록 구현해볼 것 입니다.
- 리액트에서는 조건이나 동작에 따라 변화할 수 있는 상태를 state라고 부르고, 해당 상태를 하위 컴포넌트에 props의 형태로 전달하여 사용하곤 한다.
- 다만 위의 그림처럼 상위 컴포넌트에서 하위 컴포넌트로 상태값을 전달하기 위해서는 두 컴포넌트 사이에 있는 모든 컴포넌트를 거쳐 전달해야 하는 단점이 있었는데, 이러다 보니 코드가 복잡해짐과 동시에 상태값을 전달하는 역할의 컴포넌트에서는 사용하지 않을 불필요한 속성값을 받게 된다는 단점이 있었다.
// 하위 컴포넌트 D로 state 값을 전달하는 순서 // without redux 컴포넌트 A state 컴포넌트 B 하위 전달 컴포넌트 C 하위 전달 컴포넌트 D pros // with redux [중앙 Store에서 state 관리] 컴포넌트 A 컴포넌트 B 컴포넌트 C 컴포넌트 D store.getState()- 그래서 등장한 것이 Flux 아키텍처와 이를 구현한 리덕스(Redux)로, 리덕스를 사용하면 상태값을 중앙 스토어에서 관리할 수 있어 복잡한 컴포넌트 계층 구조에서도 상태를 효과적으로 관리할 수 있다.