개발자들이 리덕스 툴킷을 쓰는 것을 권장하고 있지만, 현업에서 툴킷을 쓰느냐 묻는다면 또 그렇지는 않습니다. 결국 기본 리덕스에 대해 잘 이해하고 있어야합니다.
useReducer
과 비슷한 구조를 가지고 있습니다.action type
정의, action생성 함수
정의, reducer
정의 세가지 타입으로 나뉩니다.export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";
action type
를 미리 정의해놓는 것입니다.→ 원칙적으로 항상 액션 객체를 리턴해야 합니다
{ type: INC_COUNT, payload: ~~ }
와 같은 객체dispatch
: action 객체를 reducer
로 보내는 행위dispatch({type:’~~’})
와 같은 방식으로 dispatch 에 action 객체를 직접 명시해서 전달했습니다.//액션 함수 생성
export function incCount(diff) {
return {
type: INC_COUNT,
payload: { diff },
};
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: { diff },
};
}
dispatch(incCount(2))
→ state는 항상 불변성을 유지해야 합니다(push사용금지, concat등 사용)
→동일한 파라미터가 들어왔다면 동일한 결과를 출력해야합니다.
(data등을 사용할 경우 해당 부분은 액션 생성시 처리를 하고, 리듀서에서 그러한 랜덤 로직을 생성해서는 안됩니다.)
function counter(state = initialState, action) {
switch (action.type) {
case INC_COUNT:
return state + action.payload.diff;
case DEC_COUNT:
return state - action.payload.diff;
default:
return state;
}
}
store.js
import { legacy_createStore as createStore } from "redux";
import counter from "./reducers/counter";
const store = createStore(counter);
export default store;
App.js
import { Provider } from "react-redux";
import store from "./redux";
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
useSelector
: 관리중인 상태를 가져오기 위한 Hook
useDispatch
: dispatch함수를 실행할 수 있도록 해주는 Hook
//useSelector
import { useSelector } from "react-redux";
const { number } = useSelector((state) => state);
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
// 사용 예시
dispatch(incCount(1));
Container 컴포넌트
에서 담당Presenter 컴포넌트
에서 담당Container 컴포넌트
import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { incCount, decCount } from "../redux/actions/counter";
function CounterContainer() {
const dispatch = useDispatch();
const number = useSelector((state) => state.number);
const onIncrease = () => {
dispatch(incCount(1));
};
const onDecrease = () => {
dispatch(decCount(1));
};
return (
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
);
}
export default CounterContainer;
Presenter 컴포넌트
import React from "react";
function Counter({ number, onIncrease, onDecrease }) {
return (
<div>
<p>{number}</p>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
export default Counter;
$ yarn add redux react-redux
react-redux
컴포넌트에 리덕스모듈을 적용하기 위해 사용.constants > counter.js
: action type정의
export const INC_COUNT = "INC_COUNT";
export const DEC_COUNT = "DEC_COUNT";
actions > counter.js
: action함수 정의
import { INC_COUNT, DEC_COUNT } from "../constants/counter";
export function incCount(diff) {
return {
type: INC_COUNT,
payload: { diff },
};
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: { diff },
};
}
reducer > counter.js
: 리듀서 함수 정의
import { INC_COUNT, DEC_COUNT } from "../constants/counter";
const initialState = { number: 0 };
export default function counter(state = initialState, action) {
switch (action.type) {
case INC_COUNT:
return { number: state.number + action.payload.diff };
case DEC_COUNT:
return { number: state.number - action.payload.diff };
default:
return state;
}
}
store
정의
import { legacy_createStore as createStore } from "redux";
import counter from "../reducers/counter";
const store = createStore(counter);
export default store;
App.js
적용
import Counter from "./components/Counter";
import { Provider } from "react-redux";
import store from "./store";
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
export default App;
src 하위에 components폴더> Counter.jsx와 containers>CounterContainer파일을 만들어줍니다.
CounterContainer
import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { incCount, decCount } from "../redux/actions/counter";
function CounterContainer() {
const dispatch = useDispatch();
const number = useSelector((state) => state.number);
const onIncrease = () => {
dispatch(incCount(1));
};
const onDecrease = () => {
dispatch(decCount(1));
};
return (
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
);
}
export default CounterContainer;
Counter.jsx
function Counter({ number, onIncrease, onDecrease }) {
return (
<div>
<p>{number}</p>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
);
}
App.js
import { Provider } from "react-redux";
import store from "./redux/store/store";
import CounterContainer from "./containers/CounterContainer";
function App() {
return (
<Provider store={store}>
<CounterContainer />
</Provider>
);
}
export default App;
[규칙]
modules > counter.js
//액션타입 정의
const INC_COUNT = "counter/INC_COUNT";
const DEC_COUNT = "counter/DEC_COUNT";
//액션 생성함수
export function incCount(diff) {
return {
type: INC_COUNT,
payload: { diff },
};
}
export function decCount(diff) {
return {
type: DEC_COUNT,
payload: { diff },
};
}
const initailState = { number: 0 };
export default function counter(state = initailState, action) {
switch (action.type) {
case INC_COUNT:
return {
...state,
number: state.number + action.payload.diff,
};
case DEC_COUNT:
return {
...state,
number: state.number - action.payload.diff,
};
default:
return state;
}
}
modules>index.js
import { legacy_createStore as createStore } from "redux";
import counter from "./counter";
const store = createStore(counter);
export default store;
app.js
import { Provider } from "react-redux";
import store from "./modules";
import CounterContainer from "./containers/CounterContainer";
function App() {
return (
<Provider store={store}>
<CounterContainer />
</Provider>
);
}
container > CounterContainer
import { useDispatch, useSelector } from "react-redux";
import Counter from "../components/Counter";
import { incCount, decCount } from "../modules/counter";
function CounterContainer() {
const dispatch = useDispatch();
const number = useSelector((state) => state.number);
const onIncrease = () => {
dispatch(incCount(1));
};
const onDecrease = () => {
dispatch(decCount(1));
};
return (
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
);
}
export default CounterContainer;
components>Counter.jsx
import React from 'react'
function Counter({ number, onIncrease, onDecrease }) {
return (
<div>
{number}
<button onClick={onIncrease}>증가</button>
<button onClick={onDecrease}>감소</button>
</div>
)
}
export default Counter
만약 counter외에도 관리하는 store가 많은 경우 combineReducers를 통해 rootReducers을 만들어 관리를 할 수도 있습니다.
import { legacy_createStore as createStore } from "redux";
import { combineReducers } from "redux";
import counter from "./counter";
import user from './user';
const rootReducer = combineReducers({
counter,
user
})
const store = createStore(rootReducer);
export default store;
단, combinereducer 를 사용하고 난 뒤에는, useSelector 를 쓰는 방식이 조금 달라집니다.
state.number → state.counter.number
redux logger
는 redux 로 실행되는 로직에 대해서 logging, 즉 콘솔창에 기록을 남겨주는 역할을 담당하는 리덕스 미들웨어 (미들웨어라고 해서 특별한 것이 아니고, 그냥 리덕스가 작동하는 과정에서 store, action 객체를 다루면서 특정한 기능을 수행하는 함수를 말합니다) 입니다.
$ yarn add redux-logger --dev
기본적으로 사용하는 코드는 아래와 같습니다.
applyMiddleware 를 통해서 해당 미들웨어를 적용할 수 있습니다.
import { applyMiddleware, legacy_createStore as createStore } from "redux";
import logger from 'redux-logger';
const store = createStore(rootReducer, applyMiddleware(logger));
어떤 action 이 실행되었고, state 가 어떻게 변화했는지를 개발자 도구 콘솔에서 확인할 수 있습니다!
redux-devtools
는 크롬에서 redux 전용 개발자 도구를 활용할 수 있도록 해줍니다
확장 프로그램만 설치하면 되고, 따로 모듈을 추가로 설치할 것은 없습니다. redux logger 와 동시에 사용하기 위한 코드를 작성한다면 아래와 같습니다.
import { applyMiddleware, compose, legacy_createStore as createStore } from "redux";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(logger)));
이후 실행해보면, 아래처럼 실행된 action 과 관련한 세부사항을 확인할 수 있고, 되돌리기까지도 가능합니다!