본문 바로가기

Redux

[Redux] 🏃‍♂️ 리액트와 리덕스 함께 사용하기 (feat. ant-design) (1)

 

이 글에서 함께 만들어볼 간단한 노트앱의 최종 소스는 아래 링크에 있습니다.

 

youthfulhps/noteapp-react-redux-antd

Ant-design을 이용한 간단한 노트앱입니다. Contribute to youthfulhps/noteapp-react-redux-antd development by creating an account on GitHub.

github.com

그리고, 아래와 같은 모습의 앱을 만들 예정입니다.

 

1. create-react-app 

우선, 새로운 앱을 생성하겠습니다.

 

~$ create-react-app noteapp-redux

 

~$ npm start   //test

 

2. npm install 

ant-design UI framework, UI 디자인을 책임질 프레임워크입니다.

~$ npm install antd --save

 

ant-design IconUI 디자인의 아이콘을 담당하는 모듈입니다.

~$ npm install @ant-design/icons --save

 

 

redux, react-redux, 오늘 적용할 리덕스와 리액트에서 리덕스를 사용할 수 있게 해주는 모듈입니다.

~$ npm install redux react-redux --save

 

redux-actions, 리덕스 액션을 정의하고 액션 함수를 생성할 때 보다 더 간결하고 가독성을 높여주는 모듈입니다. 

npm install redux-actions --save

 

3. note 모듈 만들기

특정 기능을 구현하기 위해 필요한 액션, 액션 생성 함수, 상태들의 초깃값, 리듀서 함수가 들어있는 파일을 모듈이라 칭하겠습니다.

src/store/modules/note.js 파일을 새롭게 만들고 이 파일에 액션부터 리듀서까지 생성해보도록 하겠습니다.

4. 액션 타입 정의

import { createAction, handleActions } from "redux-actions";

const INPUT_TITLE = "note/INPUT_TITLE";
const INPUT_CONTENT = "note/INPUT_CONTENT";
const ADD_NOTE = "note/ADD_NOTE";
const REMOVE_NOTE = "note/REMOVE_NOTE";

 

  • INPUT_TITLE : 제목입력란에 입력이 들어올 경우 입력값을 저장하는 액션입니다.
  • INPUT_CONTENT : 내용입력란에 입력이 들어올 경우 입력값을 저장하는 액션입니다.
  • ADD_NOTE : 제목과 내용을 입력하고 파란색 플러스 버튼을 눌렀을 때 발생하는 액션입니다.
  • REMOVE_NOTE : 생성된 노트에 있는 빨간색 삭제 버튼을 눌렀을 때 발생하는 액션입니다.

5. 액션 함수 생성

액션함수를 생성할 때 위 코드에서 import 한 redux-actions의 createAction을 사용할 건데, 이는 FSA 규칙을 따르고 있습니다. 가장 특징인 것은 모든 액션 객체는 액션에서 사용할 파라미터의 필드명을 payload로 통일하는 규칙을 가지고 있습니다. 이를 통해 액션 함수를 보다 쉽게 생성해낼 수 있습니다. 처음에 너무 깊게 이해해야 한다기보다는 이런 규칙에 따라 액션 함수를 생성할 거구나 정도만 이해하고 우선 넘어가겠습니다. 

 

 

redux-utilities/flux-standard-action

A human-friendly standard for Flux action objects. - redux-utilities/flux-standard-action

github.com

위에 추가했던 코드에 이어서 아래 코드를 추가해줍니다. id값은 ADD_NOTE 액션이 발생할 때 즉, 새로운 노트가 추가될 때마다 고유의 id값을 넣어주어야 하기 때문에 추가해주었습니다.

 

let id = 0;

export const changeInputTitle = createAction(INPUT_TITLE, (title) => title);
export const changeInputContent = createAction(
  INPUT_CONTENT,
  (content) => content
);
export const addNote = createAction(ADD_NOTE, (title, content) => ({
  id: ++id,
  title,
  content,
}));
export const removeNote = createAction(REMOVE_NOTE, (id) => id);

 

6. 초기 상태 정의

마찬가지로 같은 파일에 아래코드를 추가해줍니다. (첫 노트의 제목과 내용은 간절함을 담았습니다.)

 

const initialState = {
  inputTitle: "",
  inputContent: "",
  noteList: [
    {
      id: 0,
      title: "제발 됐으면",
      content: "제발 되주세요",
    },
  ],
};

 

  • inputTitle : 제목 입력값을 저장할 상태입니다.
  • inputContent : 내용 입력값을 저장할 상태입니다.
  • noteList : 노트들의 id, title, content를 순서대로 저장할 상태입니다. 객체를 담는 배열로 선언했습니다.

7. 리듀서 함수 정의

리듀서 함수는 redux-actions에서 제공하는 handleActions을 사용하겠습니다. 더 이상 switch /case 문을 사용할 필요가 없고, 각 액션 타입마다 업데이터 함수를 구현하는 방식이기 때문에 가독성이 매우 훌륭합니다.

 

잠시, 액션을 생성할 때 사용했던 createActions 을 다시 한번 보면, 파라미터가 title, content 두 가지로 구성되어 있습니다.

 

export const addNote = createAction(ADD_NOTE, (title, content) => ({
  id: ++id,
  title,
  content,
}));

 

하지만, FSA 규칙에 의해 모든 필드명은 payload로 정의되기 때문에 아래와 같이 id는 action.payload.id, title은 action.payload.title...라고 정의됩니다.

 

export default handleActions(
  {
    [INPUT_TITLE]: (state, action) => ({
      ...state,
      inputTitle: action.payload,
    }),
    [INPUT_CONTENT]: (state, action) => ({
      ...state,
      inputContent: action.payload,
    }),
    [ADD_NOTE]: (state, action) => ({
      ...state,
      noteList: state.noteList.concat({
        id: action.payload.id,
        title: action.payload.title,
        content: action.payload.content,
      }),
    }),
    [REMOVE_NOTE]: (state, action) => ({
      ...state,
      noteList: state.noteList.filter((note) => note.id !== action.payload),
    }),
  },
  initialState
);

 

여기까지 하셨다면, 특정 기능(note)에 관한 모듈을 잘 생성해내신 겁니다!

 

 

8. 루트리듀서 정의

특정 기능(note)을 하는 모듈을 하나 생성했습니다. 우리가 만들고 있는 노트 앱은 note 관련된 기능만으로도 충분해 보이지만, 앱의 기능이 다양해진다면, 여러 개의 모듈이 생성될 것이기 때문에 이러한 reducer들을 하나로 묶어줄 필요가 있습니다. 하나로 묶인 리듀서를 루트 리듀서라 하며, 아래와 같이 작성할 수 있습니다. src/store/modules/index.js 파일을 새롭게 만들고 아래 코드를 추가해줍니다.

 

import { combineReducers } from "redux";
import note from "./note";
//다른 모듈을 더 불러오고 싶다면, 여기서 import!

export default combineReducers({
  note,
  //불러온 리듀서를 이곳에 추가!

});

 

여기까지 특정기능을 하는 하나의 모듈을 생성해보았습니다! 다음 글에서는 컴포넌트를 가능한 잘게 구성하고 생성한 모듈을 사용해 state값의 변화를 주는 방법을 알아보도록 하겠습니다.

 

최종코드

 

src/store/modules/note.js

import { createAction, handleActions } from "redux-actions";

const INPUT_TITLE = "note/INPUT_TITLE";
const INPUT_CONTENT = "note/INPUT_CONTENT";
const ADD_NOTE = "note/ADD_NOTE";
const REMOVE_NOTE = "note/REMOVE_NOTE";

let id = 0;

export const changeInputTitle = createAction(INPUT_TITLE, (title) => title);
export const changeInputContent = createAction(
  INPUT_CONTENT,
  (content) => content
);
export const addNote = createAction(ADD_NOTE, (title, content) => ({
  id: ++id,
  title,
  content,
}));
export const removeNote = createAction(REMOVE_NOTE, (id) => id);

const initialState = {
  inputTitle: "",
  inputContent: "",
  noteList: [
    {
      id: 0,
      title: "제발 됐으면",
      content: "제발 되주세요",
    },
  ],
};

export default handleActions(
  {
    [INPUT_TITLE]: (state, action) => ({
      ...state,
      inputTitle: action.payload,
    }),
    [INPUT_CONTENT]: (state, action) => ({
      ...state,
      inputContent: action.payload,
    }),
    [ADD_NOTE]: (state, action) => ({
      ...state,
      noteList: state.noteList.concat({
        id: action.payload.id,
        title: action.payload.title,
        content: action.payload.content,
      }),
    }),
    [REMOVE_NOTE]: (state, action) => ({
      ...state,
      noteList: state.noteList.filter((note) => note.id !== action.payload),
    }),
  },
  initialState
);

 

src/store/modules/note.js

import { combineReducers } from "redux";
import note from "./note";

export default combineReducers({
  note,
});