리액트 프로젝트에 리덕스를 적용하기 위해 리덕스 모듈을 만들어보았다.
리덕스 모듈이란 다음 항목들이 모두 들어있는 자바스크립트 파일을 의미한다.
- 액션 타입
- 액션 생성함수
- 리듀서
- +) 초기 상태
리덕스를 사용하기 위해 필요한 위 항목들은 각각 다른 파일에 저장할 수도 있다.
리덕스 GitHub repository 에 등록되어 있는 예제 프로젝트를 보면 다음과 같이 코드가 분리되어 있다.
위 예제에선 액션과 리듀서가 서로 다른 파일에 정의되어 있다. 하지만, 이 코드들이 꼭 분리되어 있을 필요는 없다. 오히려 개발하는 데 코드들이 분리되어 있으면 불편할 수도 있다.
따라서 우리는 리듀서와 액션 관련 코드들은 하나의 파일에 몰아서 작성하도록 하겠다. 이를 Ducks 패턴이라고 부른다. 리덕스 관련 코드를 분리하는 방식은 정해져 있지 않으므로 개발자가 자유롭게 분리해도 상관은 없다.
첫 번째 리덕스 모듈을 만들어보자. src 디렉터리에 modules 디렉터리를 만들고 그 안에 counter.js 파일을 생성하여 코드를 작성하도록 하자.
/*액션 타입 만들기*/
//액션 타입을 문자열로 만들면 액션 타입을 수정해야 할 경우 예를 들어 INCREASE -> INCREASE_PRICE,
//해당 액션이 있는 생성함수를 모두 찾아서 수정해야하는 번거로운 일이 생긴다.
//따라서 액션타입을 상수로 선언해주면 선언부만 바꿔주면 되기 때문에 더욱 효율적이다.
//Ducks 패턴을 따를때에는 액션의 이름에 접두사를 넣어준다.
//이렇게 하면 다른 모듈과 액션 이름이 중복되는 것을 방지할 수 있다.
const SET_DIFF = 'counter/SET_DIFF';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
/*액션 생성함수 만들기*/
// 액션 생성함수를 만들고 export 키워드를 사용하여 내보낸다.
// 컨테이너 컴포넌트에서 dispatch 를 이용할때 사용된다.
export const setDiff = diff => ({ type: SET_DIFF, diff });
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
/*초기 상태 선언*/
const initialState = {
number: 0,
diff: 1
}
/*리듀서 선언*/
//리듀서는 export default 로 내보낸다.
export default function counter(state = initialState, action){
switch(action.type){
case SET_DIFF:
return{
...state,
diff: action.diff
}
case INCREASE:
return{
...state,
number: state.number + state.diff
}
case DECREASE:
return{
...state,
number: state.number - state.diff
}
default:
return state;
}
}
todos 모듈도 만들어보자. 동일하게 modules 디렉터리에 todos.js 파일을 생성하고 코드를 작성한다.
/*액션 타입 선언*/
const ADD_TODO = "todos/ADD_TODO";
const TOGGLE_TODO = "todos/TOGGLE_TODO";
/*액션 함수 선언*/
let nextId = 1;
export const addTodo = text => ({
type: ADD_TODO,
todo: {
id: nextId++,// id 에 nextId 값을 넘겨준 후, nextId 값에 1을 더한다.
text
}
});
export const toggleTodo = id => ({
type: TOGGGLE_TODO,
id
});
/*초기 상태 선언*/
//리듀서의 초기 상태는 꼭 객체 타입일 필요는 없다.
//배열이어도 되고, 원시 타입 (숫자, 문자, boolean 이여도 상관없다.)
const initialState = [ ];
/*리듀서 선언*/
export default function todos(state = initialState, action){
switch (action.type){
case ADD_TODO:
return state.concat(action.todo);
case TOGGLE_TODO:
return state.map(
todo =>
todo.id === action.id
? { ...todo, done: !todo.done }
: todo
)
default:
return state;
}
}
현재 두가지의 리덕스 모듈을 만들었다. 그래서 리듀서도 두개이다. 한 프로젝트에 여러개의 리듀서가 있을 때는 이를 한 리듀서로 합쳐서 사용한다. 합쳐진 리듀서를 루트 리듀서라고 부른다.
리듀서를 합치는 작업은 리덕스에 내장되어 있는 combineReducers
함수를 사용한다.
modules 디렉터리에 index.js 를 만들고 다음과 같이 코드를 작성한다.
import { combineReducers } from 'redux';
import counter from './counter'; // ./counter 위치에서 export 하는 리듀서를 가져온다
import todos from './todos'; // ./todos 위치에서 export 하는 리듀서를 가져온다
const rootReducer = combineReducers({
counter,
todos
});
export default rootReducer;
리듀서가 합쳐졌다. 루트 리듀서가 만들어졌으면, 이제 스토어를 만들어보자.
리덕스 스토어를 만드는 작업은 src 디렉터리의 main.js 에서 해주었다
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
const store = createStore(rootReducer); //스토어를 만든다.
console.log(store.getState());//스토어의 상태를 확인한다.
ReactDOM.render(<App />, document.getElementById('root'));
스토어를 만들고, 스토어의 상태를 출력해보았다.
정상적으로 counter 와 todos 서브 리듀서들이 합쳐진 것을 볼 수 있다. 이제 console.log(store.getState());
코드는 지우도록 하자.
리액트 프로젝트에 리덕스를 적용할 때는 react-redux 라이브러리를 사용해야 한다. 해당 라이브러리를 설치하자
$ yarn add react-redux
그 다음 main.js 에서 Provider 라는 컴포넌트를 불러와서 App 컴포넌트를 감싸준다. 그리고 Provider 의 props 에 store 를 넣어준다.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
const store = createStore(rootReducer); //스토어를 만든다.
console.log(store.getState());//스토어의 상태를 확인한다.
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Provider 의 props 로 store 를 넣어서 App 을 감싸면 우리가 렌더링하는 그 어떤 컴포넌트던지 리덕스 스토어에 접근할 수 있게 된다.