이전 스터디에서 리액트, next.js 와 함께 Redux 를 쓰는 방법을 익혔었는데 단순 React 에서 Redux를 쓰는 법이 궁금해졌다. 벨로퍼트님의 모던 리액트 정리해놓은 자료를 보면서 Redux에 대해 정리해보려고한다.
루트 디렉터리에 가서
redux 와 react-redux 를 설치한다.
yarn add redux react-redux
node birds 만들기 강좌에서 리덕스를 쓸때 이런 파일 구조로 만들었었다.
saga, reducer로 나누어 다른 파일에서 관리했더니 불편한점은 하나의 기능을 수정하려고 하면 이기능과 관련된 여러개의 파일을 수정해야 하는 것이였다.
이 불편함을 개선하고자 나온 것이 Ducks 패턴이라고 한다.
Ducks 패턴은 구조 중심이 아니라 기능중심으로 파일을 나눈다. 단일 기능을 작성하거나 수정할 때 하나의 파일만 다루면 되므로 직관적인 코드 작성이 가능하다.
즉 action type, action creator 함수, saga, reducer 를 하나의 파일에서 관리하는 것이다.
// 액션타입
const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
// 액션 크리에이터 함수
export const increase = () => ({type: INCREASE});
export const decrease = () => ({type: DECREASE});
//초기값
const initialState = 0;
//리듀서
export default function counter(state = initialState, action) {
switch(action.type) {
case INCREASE:
return state + 1;
case DECREASE:
return state - 1;
default :
return state;
}
}
리듀서는 이전 상태와 액션을 받아서 다음 상태를 만들어 주는 함수이다.
그 다음 루트 리듀서를 만들어 준다.
import {combineReducers} from 'redux';
import counter from './counter';
const rootReducer = combineReducers({counter});
export default rootReducer;
combineReducers 는 redux 에서 제공하는 리듀서들을 합쳐주는 메서드이다. 리듀서(함수)를 쉽게 합치기 위해서 사용한다.
지금은 counter 리듀서(함수) 밖에 없지만 리듀서가 여러개라면
import counter from './counter';
import user from './user';
import post from './post';
const rootReducer = combineReducers({counter, user, post});
이렇게 하면 된다.
user 와 post 의 initialState 는 combineReducers 가 알아서 합쳐서 넣어준다.
프로젝트에 리덕스를 적용할 때는 src/index.js 에서 루트 리듀서를 불러와서 이를 통해 새로운 스토어를 만들고 Provider 를 사용해서 프로젝트에 적용을 한다.
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import * as serviceWorker from "./serviceworker";
import { createStore } from "redux";
import { Provider } from "react-redux";
import rootReducer from "./modules";
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
이렇게 작성하고 나니 createStore 는 deprecate 되었다고 한다.
@reduxjs/toolkit 의 configureStore 를 쓰라고 권장하고 있다. toolkit 적용은 나중에 해보도록 하고 일단 가이드 대로 대체하고 계속 진행해보자.
import { legacy_createStore as createStore } from "redux";
🚩 스토어 생성
const store = createStore(rootReducer);
🚩 Provider에 스토어값 설정하기
<Provider store={store}>
<App />
</Provider>,
프레젠테이셔널 컴포넌트는 오직 뷰만을 담당하는 컴포넌트이다. 이안에는 DOM 엘리먼트 style 을 갖고 있고 프레젠테이셔널 컴포넌트나 컨테이너 컴포넌트를 가지고 있을 수도 있다.
하지만 리덕스 스토어에는 직접적인 접근 권한이 없으며 오직 props 로 만 데이터를 가져올 수 있다. 또한 대부분의 경우 state 를 가지고 있지 않으며 갖고 있을 경우 데이터에 관련된 것이 아니라 UI에 관련된 것이여야한다. 주로 함수형 컴포넌트로 작성되며, state 를 갖고 있거나 최적화를 위해 LifeCycle 이 필요해 질때 클래스형 컴포넌트로 작성된다.
src/components/Counter.js
function Counter({ number, onIncrease, onDecrease }) {
return (
<div>
<h1>{number}</h1>
<button onClick={onIncrease}> +1 </button>
<button onClick={onDecrease}> -1 </button>
</div>
);
}
export default Counter;
프레젠테이셔널 컴포넌트들과 컨테이너 컴포넌트들을 관리하는 것을 담당한다. 주로 내부에 DOM 엘리먼트가 직접적으로 사용되는 경우가 없다. 사용되는 경우는 감싸는 용도일 때만 사용된다.
스타일을 가지고 있지 않는다. 스타일은 모두 프레젠테이션 컴포넌트에서 정의되어야한다. 상태를 가지고 있을 때가 많으며 리덕스에 직접 접근할 수 는 없다.
이를 통해 UI와 Data 가 분리되어 프로젝트를 이해하기 쉽고 컴포넌트 재 사용률도 높아진다.
src/containers/counterContainer.js
import React from "react";
import Counter from "../components/Counter";
import { useSelector, useDispatch } from "react-redux";
import { increase, decrease } from "../modules/counter";
function CounterContainer() {
const number = useSelector((state) => state.counter);
const dispatch = useDispatch();
const onIncrease = () => {
dispatch(increase());
};
const onDecrease = () => {
dispatch(decrease());
};
return (
<Counter number={number} onIncrease={onIncrease} onDecrease={onDecrease} />
);
}
export default CounterContainer;
로컬 서버를 실행하면 위와같이 뜨는 것을 확인할 수 있다.
https://velog.io/@dolarge/React-Redux-Ducks-%ED%8C%A8%ED%84%B4
https://redux.vlpt.us/1-2-presentational-and-container-components.html