자바스크립트 프레임워크인 앵귤러,뷰와 달리 라이브러리인
리액트
는 자체적으로 상태관리 도구를 제공하지 않기 때문에 상태관리를 위해서 'Redux'와 사용된다. 리액트에 redux를 적용하여 사용해보자.
리덕스를 사용할 때 가장 많이 사용하는 패턴은 프레젠테이션 컴포넌트 + 컨테이너 컴포넌트를 분리하는 것이다. 이 패턴을 사용하면 코드의 재사용성이 높아지는 장점이 있다.
가장 일반적인 구조는 actions,constants,reducers기능별로 파일을 하나씩 만드는 방법이다. 하지만 새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야하는 불편함이 있다. 그래서 생겨난 것이 액션타입, 액션생성함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 작성하는 ducks 패턴
방식이다.
1. 액션타입 정의
액션 타입은 대문자, 문자열 내용은 '모듈 이름/액션 이름'과 같은 형태로 작성하자. 프로젝트가 커졌을 때 액션의 이름이 충돌하지 않기 위해서 모듈이름을 넣는 것이 좋다.
//couter.js
모듈이름/액션이름
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
2. 액션 생성 함수
//couter.js
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
3. 초기상태 && 리듀서 함수
//couter.js
const initialState = {
number: 0,
};
function counter(state = initialState, action) {
switch (action.type) {
case INCREASE:
return {
number: state.number + 1,
};
case DECREASE:
return {
number: state.number - 1,
};
default:
return state;
}
}
//todos.js
//액션 타입 정의하기
const CHANGE_INPUT = "todos/CHANGE_INPUT"; //인풋 값을 변경
const INSERT = "todos/INSERT"; // 새로운 todo 등록
const TOGGLE = "todos/TOGGLE"; //todo를 체크 or 체크해제
const REMOVE = "todos/REMOVE"; // todo를 제거
//액션 생성 함수 만들기 (파라미터 필요)
export const changeInput = (input) => ({
type: CHANGE_INPUT,
input, //액션 객체 안에 추가 필드로 넣음
});
let id = 3;
export const insert = (text) => ({
type: INSERT,
todo: {
id: id++,
text,
done: false,
},
});
export const toggle = (id) => ({
type: TOGGLE,
id,
});
export const remove = (id) => ({
type: REMOVE,
id,
});
//초기상태 및 리듀서 함수
const initialState = {
input: "",
todos: [
{ id: 1, text: "리덕스 기초 배우기", done: true },
{ id: 2, text: "리액트 + 리덕스", done: false },
],
};
function todos(state = initialState, action) {
switch (action.type) {
case CHANGE_INPUT:
return {
...state,
input: action.input,
};
case INSERT:
return {
...state,
todos: state.todos.concat(action.todo),
};
case TOGGLE:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo,
),
};
case REMOVE:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.id),
};
default:
return state;
}
}
export default todos;
스토어를 만들 때에는 리듀서를 하나만 사용해야 한다.'counter','todos'두 개의 리듀서를 만들었기 때문에 이것들을 하나로 합쳐주는 작업이 필요하다. 함수인 리듀서를 객체처럼 합칠 수 없기 때문에 리덕스에서 제공하는 combineReducers
라는 유틸 함수를 사용하면 쉽게 처리할 수 있다.
import { combineReducer } from "redux";
import counter from "./counter";
import todos from "./todos";
const rootReducer = combineReducer({
counter,
todos,
});
export default rootReducer;
최상단인 src디렉터리 index.js에서 적용. 스토어 생성 후, provider 컴포넌트로 app컴포넌트를 감싼다.
위에서 생성한 rootReducer를 store에 첫번째 인자로 넘겨줘서 couter, todos 두 개의 리듀서가 컴바이닝되고 스토어로 사용할 수 있게 된다.
리덕스 개발자 도구로 리덕스 스토어 내부 상태를 확인할 수 있다.
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import "./index.css";
import App from "./App";
import rootReducer from "./containers/modules";
import { Provider } from "react-redux";
import { composeWithDevTools } from "redux-devtools-extension";
const store = createStore(rootReducer, composeWithDevTools());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root"),
);
리덕스 스토어와 연동된 컨테이너 컴포넌트
를 통해 컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태값을 받아오고, 액션도 디스패치할 수 있다.
import React from "react";
import { connect } from "react-redux";
import { increase, decrease } from "../modules/counter"; //액션함수 불러오기
import Counter from "../components/Counter"; // 연결할 ui 코드 부분 가져오기
const CounterContainer = ({ number, increase, decrease }) => {
return (
<Counter number={number} onIncrease={increase} onDecrease={decrease} />
);
};
const mapStateToProps = (state) => ({ number: state.counter.number });
const mapDispatchToProps = (dispatch) => ({
increase: () => {
dispatch(increase()); //액션함수 불러오기
},
decrease: () => {
dispatch(decrease());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);
ui코드를 담당하는 Counter를 가져온다.
컴포넌트와 리덕스를 연결하려면 connect 함수를 사용해야한다. connect함수는 다음과 같이 구성된다.
connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)
mapStateToProps, mapDispatchToProps를 선언하지 않고, connect 함수 내부에 익명 함수 형태
로 선언하는 방법도 있다. 코드가 더 깔끔해보이는 효과도 있고, 취향에 따라 선택하면 될 것 같다.
export default connect(
(state) => ({ number: state.counter.number }),
(dispatch) => ({
increase: () => dispatch(increase()),
decrease: () => dispatch(decrease()),
}),
);
앞에 방법보다 편한 방법으로 mapDispatchToProps에 해당하는 파라미터를 함수 형태가 아닌 액션 생섬 함수로 이루어진 객체 형태로 넣어주는 것이있다.
export default connect((state) => ({ number: state.counter.number }), {
increase,
decrease,
})(CounterContainer);
두 번째 파라미터를 객체 형태로 넣어주면 connect함수가 내부적으로 *bindActionCreators 작업을 대신해준다.
*bindActionCreators:액션생성함수가 많을 경우 일일히 dispatch로 감싸주는 작업이 번거롭게 느껴질 수 있다. 이때 리덕스에서 제공하는bindActionCreators유틸 함수를 사용하면 간단해진다.
import {bindActionCreators} from 'redux';
...생략
dispatch =>
bindActionCreators(
{액션함수1,액션함수2,...,
},
dispatch,
),
)(연동할 컴포넌트)
연정님은 정말 꾸준히 노력하시는 것 같아요 ㅎㅎ 오랜만에 왔는데도 계속 이어지는 블로그 포스트 잘 보고 갑니다. 항상 응원합니다 !