자바스크립트에서 관리해야 할 상태가 많아져 상태 관리의 어려움이 생기게 되었다.
컴포넌트 수 | 리덕스 | 일반 |
---|---|---|
2개 | 4개 상태관리 | 4개 상태관리 |
4개 | 8개 상태관리 | 16개 상태관리 |
6개 | 12개 상태관리 | 36개 상태관리 |
이처럼 리덕스를 사용하지 않으면 상태 관리를 할 때, 기하급수적으로 많은 상태를 관리해야 하는 문제가 생긴다.
자바스크립트 어플리케이션에서 흔히 쓰이는 상태 컨테이너로, REDUX를 통해 상태 관리를 컴포넌트의 바깥에서 관리 할 수 있다.
REDUX의 흐름은 다음과 같다.
Component
->Action
->Reducer
->Store
화면에 보여지는 앱(view)
dispatch
[Component-> Action] : 액션을 발생시킴
상태변화를 일으키기 위해서 액션은 바뀔 부분을 지시하고 그런 변경에 필요한 데이터를 제공한다.
출처: https://12bme.tistory.com/493 [길은 가면, 뒤에 있다.]
handle
[Action->Reducer] : action에 정의되어있는 내용이 reducer에 의해 핸들링됨
이전 상태(state), Action을 받아서 변화를 일으킨다
update
[Reducer->Store] : 핸들링에 따라서 상태값이 업데이트됨
현재 상태, Reducer 등 상태에 관한 데이터를 store에 담는다.
subscribe
[Store->Component] : 업데이트된 스토어를 subscriber를 통해 실시간으로 받아와서 사용
어...이해가 안가요. (사실 저도 redux 공부하기 위해서 3일을 투자했다는..)
네 정상이에요. 그럼 한번 비유를 들어보겠습니다.
리덕스가 없는 세상은 택배가 존재하지 않는 세상이에요.
그래서 모든 일이 직거래로만 일어납니다.
리덕스가(택배 회사)가 존재한다면? 우리가 흔히 아는대로 택배를 주문할 때 다음과 같은 과정을 거칩니다.
고객
그 자체
원하는 물품을 선택
해 주문한다.
직원이 주문을 받고 물품을 가져와 포장
한다.
현재 고객의 정보
와 물품
은 모두 택배 회사의 서버에 저장되어있다.
여기까지가 제가 redux
를 최대한 이해하기 쉽게 비유한 내용이며, 자세히 파고들면 약간은 달라질 수 있지만 우선 redux를 막 공부하는 입장에서는 '아 이런식으로 redux가 흘러가는구나.'라고 생각해 주세요.
실습의 목표는 다음과 같다.
웹페이지상에 Point 점수가 표시되고, 버튼을 통해 Point점수를 증가시킬 수 있다.
yarn add ㅁㅁㅁ
npm install ㅁㅁㅁ
터미널에서 ㅁㅁㅁ 부분에 다음과 같은 라이브러리를 넣어 실행시켜주면 기본적인 redux 라이브러리를 다운받을 수 있다.
'react-redux'
'redux'
import React from 'react';
import Point from './Point';
const App = () => {
return (
<div className="App">
<Point />
</div>
)
}
export default App;
기본적인 App.js 를 만들었고, App 클래스 내부에는 Point클래스가 들어가있다.
import React from 'react';
const Point = () => {
return (
<div className="Point">
<h2>점수: </h2>
<button>point 획득</button>
</div>
)
}
export default Point;
Point 컴포넌트는 점수를 나타내는 <h2>
태그와, 그러한 점수를 증가시켜주는 <button>
이 존재한다.
REDUX에서 action, reducer, store는 필수적인 존재이다.
위와 같이, src폴더에 redux
폴더를 생성해주고, 내부에 action.js
, reducer.js
, store.js
를 만들어놓는다.
자, 여기서부터 정신을 놓지 말고 집중해야 한다.(중요!!)
상태 변화(여기서는 Point 증가)를 일으키기 위해
Point.js
컴포넌트에서는 action
를 실행하라고 dispatch(action 실행해주세요!)를 보낸다.
const ADD_POINT = 'ADD_POINT';
export const addPoint = () => {
return {
type: ADD_POINT
}
}
Point 증가를 주기 위해, ADD_POINT
를 type로 지정한다.
여기서 들었던 의문.
엥 왜 굳이 열심히 action.js 파일을 만들어놓고, 하는 일이 type 반환밖에 없어요?
이러한 이유는 바로 다음에 나오는 reducer.js
에서 찾아볼 수 있다.(reducer는 action과 state을 받아 동작한다.)
상태에 어떠한 변화가 필요했을 때, action
을 발생시켰다면,
reducer
는 이러한 action
과 현재의 상태
를 받아, 변화를 일으킨다.
const ADD_POINT = 'ADD_POINT';
const init = {
count: 7
}
const pointReducer = (state = init, action) => {
switch (action.type) {
case ADD_POINT:
return {
...state,
count: state.count + 1
}
default: return state;
}
}
export default pointReducer;
여기서 pointReducer는 (현재의 상태, 액션)와 같이 2가지의 파라미터를 받으며,
현재의 상태가 없을 경우를 대비해, init
과 같은 기본적 상태를 미리 정의해 두었다.
reducer
는 앞서 action에서 만들었던 type을 기반으로 하여 switch문을 통해 원하는 타입(여기서는 ADD_POINT를 통해 포인트 증가가 목적이므로 ADD_POINT)을 선택한다.
...state
를 사용하는 이유는?
Reducer는 전체 state를 하나로 반환해야하기 때문에
...state로 현재 이 액션과 상관없는 state값들을 보존시켜줘야한다.
자 이제 거의 마지막 단계이다.
우리가 힘들게 action
과 reducer
를 통해 상태를 변화시켰다면, 이러한 상태에 관한 데이터를 store에 담아주어야 한다.
import { createStore } from "redux";
import pointReducer from "./reducer";
const store = createStore(pointReducer);
export default store;
redux 라이브러리에는 createStore가 기본적으로 존재하는데, 이를 사용해 우리가 만들었던 redux를 파라미터로써 넣어준다.
마무리라 이름이였지만 사실 조금의 과정이 더 남아있다.
//import React from 'react';
import { Provider } from 'react-redux';
import Point from './Point';
import store from './redux/store';
//const App = () => {
// return (
<Provider store={store}>
// <div className="App">
// <Point />
// </div>
</Provider> /* store와의 연동을 위해 Provdier 추가 */
// )
//}
//export default App
앞서 App.js와 달라진 부분은 주석가 되지 않은 부분이다.
store와 컴포넌트의 연동을 위해서 Provider
를 사용한다.
Provier: react-redux 라이브러리에 내장되어있는, 리액트 앱에 store 를 손쉽게 연동 할 수 있도록 도와주는 컴포넌트입니다.
//import React from 'react';
import { connect } from 'react-redux';
import { addPoint } from './redux/action';
const Point = (props) => {
// return (
// <div className="Point">
<h2>점수: {props.count}</h2>
<button onClick={() => props.addPoint()}>point 획득</button>
// </div>
// )
//}
const mapStateToProps = (state) => {
return {
count: state.count
}
}
const mapDispatchToProps = {
addPoint
}
export default connect(mapStateToProps, mapDispatchToProps)(Point);
점수를 변화해주기 위해서 props를 통해 count를 가져왔다.
또한 onClick
이벤트 함수에서는 addPoint
action을 props로 받아서 사용하였다.
mapStateToProps
: 컴포넌트에 props로 넣어줄 리덕스 스토어 상태에 관련된 함수
mapDispatchToProps
: 컴포넌트에 props로 넣어줄 액션을 디스패치하는 함수들에 관련된 함수
Point 컴포넌트의 props.count 는 mapStateToProps
를 통해 store
에서 count 상태를 가져오게 되고,
props.addPoint() 는 mapDispatchToProps
를 통해 store
에 접근한 컴포넌트가 dispatch를 사용할수 있게 만들어준다.
혹시라도 위에서 실습할 때 사용한 코드가 필요하다면 제 github에 오셔서 확인할 수 있습니다.
링크: https://github.com/OseungKwon/practice-react/tree/main/REDUX%20이해하기
여기까지가 3일동안 열심히 공부한 redux를 정리한 내용이며, 이제 막 redux를 배운 단계이기에 일부 비유나 개념에 약간의 오류가 있을 수도 있습니다.
혹시 잘못된 부분이 있으면 언제든지 알려주시기 바랍니다.!
참고 :
https://www.youtube.com/watch?v=wSbjROmXTaY&list=PLpJDjPqxGWGoqwd8JkvC_V0zPjJRmbRsI&index=2 code Scalper님 유튜브 일부분 참고