리액트에서 상태 관리를 할 수 있는 방법은 여러가지 방법들이 존재합니다. 오늘은 그중 redux에 대해 알아보려고 합니다. 진화된 상태관리 툴들이 많지만, 아직까지 채용공고에서 redux, mobx를 사용하는 회사들이 많은걸로 알고 있습니다. 오늘은 react-redux/toolkit 등장배경과 사용법에 대해 알아보고자 합니다.
redux는 리액트 뿐만 아니라 다른 뷰 라이브러리에서도 사용할 수 있는 상태 관리 툴입니다. 흔히 알고 있는 react-redux는 독립적인 react와 redux를 연결해주는 리액트 전용 라이브러리입니다.
react-redux는 상위 컴포넌트에서 하위 컴포넌트로 state를 전달할 때 props drilling이 깊어져 가독성이 떨어지는 것을 막고, 필요한 컴포넌트 어디서든 prop으로 state 전달하지 않고 전역에서 가져다가 사용하는걸 가능하게 해줍니다.
이름에서도 알 수 있듯이 redux의 탄생 배경을 알기 위해선 flux 패턴 먼저 이해해야 합니다. flux 패턴은 mvc 패턴의 단점을 보완할 수 있습니다.
아래 그림과 같이 각 주체는 서로 양방향 통신이 가능한 만큼 복잡합니다. 서로 어떻게 영향을 주는지 가독성이 떨어집니다. 예상치 못한 에러가 발생했을 때 디버깅하는 과정이 복잡하다는 단점으로 이어지는데요, 이를 위해 보완할 수 있는 방법이 바로 flux 패턴입니다.
Flux 패턴은 페이스북에서 만든 패턴으로 아래 그림과 같이 단방향의 데이터 흐름을 활용하여 mvc 패턴에서 발생하는 문제를 보완합니다. 페이스북에서 사용자가 인터페이스 화면에서 사용자가 채팅을 읽었음에도 불구하고 채팅 알림 표시가 사라지지 않고 중복으로 발생하는 문제를 해결하는데 도움이 되었다고 합니다.
flux 패턴을 이용하는 리덕스 역시 store와 dispatch, action와 같은 관련 api를 활용합니다.
고유 색을 지닌 상자는 컴포넌트를 의미합니다. 특정 상자 내부에 있는 상자들은 해당 컴포넌의 자식 컴포넌트입니다. (ex. 최상위 > 왼1 > 왼2 > 왼3)
오3 컴포넌트에 있는 +버튼을 클릭하면 왼3의 숫자가 1씩 증가합니다. redux를 사용하지 않는다면 최상위에서 숫자 state와 클릭이벤트를 props로 전달해야 하며 이는 버튼 클릭시 전체 컴포넌트 렌더링이 발생합니다. (사용자 버튼 클릭하면 해당 이벤트는 오3→오2→오1→최상위 전달, 이벤트에 대한 state 변경은 또다시 최상위→왼1→왼2→외3으로 전달)
이는 실제 state가 적용되지 않는, 즉 변화가 발생하지 않는 컴포넌트들까지 랜더링을 발생하여 비효율적입니다.
redux는 flux 패턴 그림과 같이 전역 저장소인 store에 state와 action(비즈니스 로직)을 저장하고, dispatch로 전달되는 action type에 맞는 action을 실행할 수 있게 해줍니다. 이는 위와 같이 상하위 컴포넌트를 타고타고 props로 전달하지 않도록 해줍니다.
import Left1 from "./Components/left";
import Right1 from "./Components/right";
import styled from "styled-components";
// redux 사용법 1. createStore를 import 해줍니다 & store는 수정하면 안되기 때문에 store라는 변수에 담아줍니다.
import { createStore } from 'redux';
// redux 사용법 4. redux 3인방을 import 해줍니다. (Provider:어떤 컴포넌트에 state를 제공할지 울타리로 정의하는 것, useSelector:어떤 state를 쓸지 선택, useDispatch:state값을 변경할때 사용, connect)
import { Provider } from 'react-redux';
// redux 사용법 2. reducer를 만들어줍니다. reducer는 store 안에 있는 state를 어떻게 바꿀것인가를 결정합니다.
function reducer(currentState, action) {
if(currentState === undefined) {
return {
number: 1
}
}
const newState = {...currentState};
// redux 사용법 7. dispatch가 전달한 action의 type을 만들어줍니다.
if(action.type === 'PLUS') {
newState.number++;
}
return newState;
}
// redux 사용법 3. createStore 함수의 인자로 reducer를 넣어줍니다.
const store = createStore(reducer);
function App() {
return (
<S.Container>
<h1>최상위</h1>
<S.Grid>
{/* redux 사용법 5. state를 사용하고 싶은 컴포넌트 바깥쪽에 Provider라는 울타리를 설치합니다.
이때 store라는 prop으로 createStore를 담아준 store를 전달합니다. */}
<Provider store={store}>
<Left1/>
<Right1/>
</Provider>
</S.Grid>
</S.Container>
);
}
export default App;
import { useSelector } from "react-redux";
function Left3() {
// redux 사용법 4. useSelector로 redux가 관리하는 state와 무선 연결해줍니다. 인자로 받는 함수를 만들어줍니다.
const number = useSelector((state) => state.number);
return (
<div>
{/* redux 사용법 5. useSelector로 찾은 값을 (담은 변수를) 나타내줍니다. */}
<h2>왼3 : {number}</h2>
</div>
)
}
export default Left3;
import { useDispatch } from "react-redux";
function Right3() {
// redux 사용법 5. onClick 이벤트를 사용하기 위해서 action을 다룰수 있는(? 내피셜) useDispatch를 사용합니다.
const dispatch = useDispatch();
return (
<div>
<h2>오3</h2>
<input type="button" value="+" onClick={() => {
// redux 사용법 6. useDispatch를 사용해서 dispatch를 가져옵니다.
dispatch({type: 'PLUS'});
}}></input>
</div>
)
}
export default Right3;
공식문에서 권장하는 바는 RTK redux-toolkit 입니다 :)
아래와 같이 react redux의 불편한 점을 해결하고자 나온 라이브러리가 "redux toolkit"입니다.
설치 npm i @reduxjs/toolkit
+버튼을 누르면 옆에 있는 숫자가 1씩 증가하는 간단한 로직을 rtk로 구현하는 방법입니다.
// rtk 사용법 1. 기존 react-redux와 같이 createStore, Provider 지정까지 동일하게 해줍니다.
// rtk 사용법 2. 큰 store 안에 작은 단위의 store 저장소인 slice를 만들기 위해 createSlice를 import합니다.
import { configureStore} from '@reduxjs/toolkit';
import counterSlice from './counterSlice';
// rtk 사용법 5. slice 내 각각의 reducer들을 합쳐서 더 큰 단위로 만든 하나의 reducer를 store 저장소에 담아줍니다.
const store = configureStore({
reducer: {
counter1: counterSlice.reducer // counterSlice 안에 있는 reducer들(up, down..)
}
})
export default store;
// rtk 사용법 4. 각각의 slice를 담는 store를 만드는 configureStore를 import 해줍니다.
import {createSlice} from '@reduxjs/toolkit';
// rtk 사용법 3. createSlice로 slice의 이름과 초기값, reducers('s' 주의!)를 만들어 줍니다.
export const counterSlice = createSlice({
name: 'counterSlice',
initialState: {value:0},
reducers: {
// reducers라는 키의 값으로 또다시 객체를 지정해줍니다.
// 그 안에는 action type별 메서드들이 들어갑니다.
up:(state, action) => {
state.value = state.value + action.payload;
},
down:(state, action) => {
state.value = state.value - action.step;
}
}
}
);
export default counterSlice;
import {Provider} from 'react-redux';
import store from './store';
import Counter from './Components/Counter';
function App() {
return (
// rtk 사용법 6. Provider의 prop으로 store를 설정해줍니다.
<Provider store={store}>
<div className="App">
<Counter />
</div>
</Provider>
);
}
export default App;
import {useSelector, useDispatch} from 'react-redux';
import counterSlice from '../counterSlice';
export default function Counter() {
// rtk 사용법 7. useSelector 사용해서 state 값을 가져옵니다.
const count = useSelector((state) => {
console.log(state); // App.js 31번째줄의 "counter1" =>전역 store(slice store X)를 만들고자 configureStore의 인자로 들어간 reducer 키값인 객체 내에 있는 counter1.
return state.counter1.value;
});
// // rtk 사용법 8. 클릭 이벤트가 있을 경우 useDispatch 사용합니다.
const dispatch = useDispatch();
return (
<div>
<button onClick={() => {
// rtk 사용법 8. type으로 createSlice에서 지정한 slice의 name 뒤에 '/'를 붙이고 사용할 action type을 정합니다.
// dispatch({type:'counterSlice/up', step:2})
// 다른 방법 ! => reducer에 전달할때 아예 type을 지정하고 인자로 payload를 지정해서 전달해줍니다.
dispatch(counterSlice.actions.up(2));
}}>+</button> {count}
</div>
);
}
https://ko.redux.js.org/introduction/getting-started#react-redux-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0
https://www.youtube.com/watch?v=yjuwpf7VH74
https://www.youtube.com/watch?v=9wrHxqI6zuM
https://velog.io/@inwoong100/React-%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-MVC-%ED%8C%A8%ED%84%B4%EA%B3%BC-FLUX%ED%8C%A8%ED%84%B4