앞서 다뤘던 Redux와 react-redux 라이브러리를 이용한 간단한 카운터 예제를 작성해보았다..😨
나는 함수형 컴포넌트를 선호하는데 사람들은 죄다 클래스형 컴포넌트 예제를 작성해놔서.. 예제 찾다가 머리가 다 빠질 뻔 했다..
일단 첫번째는 내가 잘 사용하지 않는 개인적으로 복잡하다고 생각되는
기본 Redux + react-redux (Provider, connect) 예제를 다룰 것이고, 두번째로는 내가 주로 사용하는 기본 Redux + react-redux (useSelector, useDispatch) 예제를 다룰 것이다 🤺
우선 예제는 위와 같은 구조를 띄고 있으며 각각의 코드들을 살펴보자!
actions/index.js
// action의 type을 정의하여 export
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
// 각각의 action을 실제로 정의
export const actionincrement = () => {
return {
type:INCREMENT
};
};
export const actiondecrement = () => {
return {
type:DECREMENT
};
};
첫번째로 actions/index.js
파일에는 카운터 예제에서 필요한 action들을 선언 및 정의하여 export를 통해 외부로 내보내준다.
이 예제에선 숫자 증가를 의미하는 INCREMENT
와 감소를 의미하는 DECREMENT
, 두개의 action만 존재한다.
reducers/index.js
import { combineReducers } from 'redux';
import { INCREMENT, DECREMENT } from '../actions';
const initialState = {
value : 0
}
const reducerCount = (state = initialState, actions) => {
switch(actions.type){
case INCREMENT:
return {
...state,
value : state.value + 1
};
case DECREMENT:
return {
...state,
value : state.value - 1
};
default:
return state;
}
}
const reducer = combineReducers({
reducerCount
});
export default reducer;
reducers/index.js
파일에는 카운터 예제에서 필요한 reducer를 작성한 파일이다. 또한 const initialState
라는 초기 State값도 선언해주었다. 각각의 action에 맞는 값 변경을 넣어주면 된다.
이때
...state
는 불변성을 지키기 위한 코드이다. 이 코드가 의미하는 것은 기존의 state 값들을 복사 한 뒤 value라는 키의 값만 변경하여 state를 반환하는 것이다!
const reducer = combineReducers({ reducerCount });
이 부분은 사실 reducer가 여러개 존재할 경우에 하나의 reducer로 합치기 위한 코드이지만 예제에서도 한번 사용해보았다!
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import reducer from './reducers';
const store = createStore(reducer);
ReactDOM.render(
<Provider store = {store}>
<App />
</Provider>,
document.getElementById('root')
);
src/index.js
파일에서는 redux에서 제공하는 createStore
를 이용하여 store를 선언해준다. 이때 앞에서 만든 reducer를 넣어줘야 한다. 또한 react-redux에서 제공하는 Provider
를 이용하여 해당 store를 App에게 전달해주고 있다.
src/App.js
import React from 'react';
import Counter from './components/Counter';
import Button from './components/Button';
import { connect } from 'react-redux';
import { actionincrement, actiondecrement} from './actions';
const App = (props) => {
return (
<div style={{textAlign: 'center'}}>
<Counter value={props.value}/>
<Button onClickIncrease={props.onClickIncrease}
onClickDecrease={props.onClickDecrease}/>
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
onClickIncrease: () => { dispatch(actionincrement())},
onClickDecrease: () => { dispatch(actiondecrement())}
});
const mapStateToProps = (state) => {
return {
value: state.reducerCount.value
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
src/App.js
파일에는 뒤에 나올 component들에 redux를 적용하기 위한 두가지 함수가 존재하는데
mapDispatchToProps
: 앞에서 생성된 Action Creator를 Dispatch 하기 위한 함수이다. Action을 Dispatch 하게 되면 Action은 Reducer에게 신호를 주게 된다.
mapStateToProps
: Reducer에서 새롭게 갱신된 State를 Store에게 넘겨준다.
Reducer에서 먼저 초기 state를 선언하고 이 값을 mapStateToProps에 넘겨준다. 그 후에 Store에 해당 값들을 전달시켜 등록되어 있는 Component의 상태와 State를 저장한다. 이후 mapDispatchToProps를 호출하여 생성된 함수를 연결된 Componenet에게 props 형식으로 전달하게 된다.
최종적으로 App Componenet는 Reducer의 State와 mapDispatchToProps의 함수를 자신의 props에 받아와서 연결되어 있는 Component에게 전달해주는 역할을 하는 것이다.
components/Counter.js
import React from 'react';
const Counter = ({value}) => {
return(
<div>
<h1>현재 값</h1>
<h1>{value}</h1>
</div>
)
};
export default Counter;
components/Counter.js
파일에는 카운터 예제의 숫자 값을 출력하는 부분을 담당하는 파일이다. App.js 파일로 부터 넘겨받은 value
값을 출력한다.
components/Button.js
import React from 'react';
const Button = ({onClickIncrease, onClickDecrease}) => {
return (
<div>
<button onClick={onClickIncrease}>+</button>
<button onClick={onClickDecrease}>-</button>
</div>
);
};
export default Button;
components/Button.js
파일에는 카운터 예제의 값을 증가시키고 감소시키는 버튼 부분을 담당하는 파일이다. App.js 파일로 부터 넘겨받은 함수들을 각각의 onClick
에 연결시켜주어 작동하도록 한다.
두번째 예제는 첫번째 예제와 달라진 부분의 코드만 다뤄볼 것이다.
내가 주로 사용하는 방식인 두번째 예제의 경우에는 구조가 조금 달라졌는데 reducers/index.js
하나만 존재하던 방식에서 reducers/counter.js
로 쪼개주었다.
reducers/counter.js
import { INCREMENT, DECREMENT } from "../actions";
const initialState = {
value : 0
}
const counter = (state = initialState, actions) => {
switch(actions.type){
case INCREMENT:
return {
...state,
value : state.value + 1
};
case DECREMENT:
return {
...state,
value : state.value - 1
};
default:
return state;
}
}
export default counter;
reducers/index.js
import { combineReducers } from 'redux';
import counter from './counter';
const rootReducer = combineReducers({
counter
});
export default rootReducer;
combineReducers
에 의해 통합된 reducer 이름을 rootReducer로 변경해준 것 이외에는 코드상에 큰 차이는 없다.
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import {createStore} from 'redux';
import rootReducer from './reducers';
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store = {store}>
<App />
</Provider>,
document.getElementById('root')
);
reducer의 구조와 이름이 달라졌기 때문에
const store = createStore(reducer);
=> const store = createStore(rootReducer);
로 변경해주었다.
src/App.js
import React from 'react';
import Counter from './components/Counter';
import Button from './components/Button';
const App = () => {
return (
<div style={{textAlign: 'center'}}>
<Counter />
<Button />
</div>
);
};
export default App;
가장 큰 변화가 생긴 파일중 하나인 src/App.js
는 기존의 mapDispatchToProps
, mapStateToProps
, connect
같은 부분이 전부 사라지고 Counter, Button Componenet를 화면에 출력하는 역할만 하도록 변경되었다.
componenets/Counter.js
import React from 'react';
import { useSelector } from 'react-redux';
const Counter = () => {
const value = useSelector((state)=>state.counter.value);
return(
<div>
<h1>현재 값</h1>
<h1>{value}</h1>
</div>
)
};
export default Counter;
props를 통해 값을 전달받던 첫번째 예제와는 다르게 useSelector를 이용하여 앞에서 생성했던 counter Reducer의 state의 value값을 직접 가져올수있다.
componenets/Button.js
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { actiondecrement, actionincrement } from '../actions';
const Button = () => {
const dispatch = useDispatch();
const onClickIncrease = useCallback(() => {
dispatch(actionincrement());
},[]);
const onClickDecrease = useCallback(() => {
dispatch(actiondecrement());
},[]);
return(
<div>
<button onClick={onClickIncrease}>+</button>
<button onClick={onClickDecrease}>-</button>
</div>
);
};
export default Button;
componenets/Button.js
에선 useDispatch와 useCallback(특정 함수를 재사용할 때 사용)을 이용하여 버튼이 클릭되게 되면 앞에서 만들었던 Action들과 각각 Dispatch 되도록 한다.
전체 코드는 여기서 확인이 가능하다.