홍준혁

React X Redux 투두리스트 예제 본문

Redux

React X Redux 투두리스트 예제

홍준혁 [Hong-JunHyeok] 2021. 1. 17. 17:03
728x90

리액트에서 리덕스 구조를 이해하려면 투두 리스트만큼 가성비 좋은 게 없다.

그럼 간단히 리액트에서 투두리스트를 리덕스로 구현해보자.

 

먼저 src의 구조는 

 

이렇게 크게 세가지로 나뉜다.

modules는 액션 타입 , 액션 생성 함수 , 리듀서를 한 파일에서 관리하는 Ducks패턴을 모아놓은 디렉터리라고 생각하자.

즉, 기존에는 액션 타입 , 액션 생성 함수 , 리듀서이 세 가지를 각각 다른 파일에 나눠서 개발했다면 Ducks패턴은 그걸 짬뽕시켜놓은 개념이다.

 

Ducks 패턴의 장점은 새로운 액션을 만들때 번거로운 작업을 하지 않아도 된다.

 

이제 component에서 간단히 input , button , ul만 태그 해놓자.

<>
	<input type="text"/>
  	<button>제출</button>
    	<ul></ul>
</>

 

그다음에 modules폴더에 todos.js라는 파일을 만들어서 액션 타입 , 액션 생성 함수 , 리듀서를 정의해보자.

 

먼저 액션 타입.

//액션 타입 정의
const INSERT = 'todos/INSERT'; //새로운 todo를 등록함
const TOGGLE = 'todos/TOGGLE'; //done 상태를 체크함
const REMOVE = 'todos/REMOVE'; //todo를 삭제함

이렇게 constants에 액션 타입을 저장해놓은 이유는 변수로 지정했을 때, 오타가 난 것을 바로 알 수 있기 때문이다.

 

이제 액션 생성 함수를 정의할 건데, 이는 객체를 만들어준다고 이해하자.

//액션 생성 함수 만들기
//전달받은 파라미터는 액션 객체 안에 추가필드로 들어감

let id = 1; //todo가 추가될때마다 id값이 증가함.
//각 todo마다 유니크한 값을 부여하기 위함
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,
});

 

보다시피, 액션 함수가 실행되면 하나의 객체를 반환하는 모습을 볼 수 있다.

 

그다음에는 리듀서를 정의해보자.

//초기 state 및 리듀서 정의
const initialState = {
  todos: [],
};

function todos(state = initialState, action) {
  switch (action.type) {
    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;
  }
}

 

각 액션 타입별로 할 일을 구분해주는 것이 리듀서의 역할이다.

todos리듀서는 state와 action을 받는데, state는 변하기 전의 상태를 의미하고 action은 액션 함수를 의미한다.

 

그렇게 한 다음에 만든 리듀서를 꼭 

export default todos

라고 해주자.

 

이제 리덕스 파일은 거의 다 만들었다고 봐도 무방하다.

src에 있는 index.js에 가서

import { createStore } from 'redux';
import { Provider } from 'react-redux';

 

이 두 개를 불러와주는데, 

createStore는 아까 만들어준 리듀서를 파라미터로 받아서 스토어로 만들어주는 함수이다.

Provider는 공급자인데, 프로바이더 하위에 store를 뿌려주는 역할을 한다.

 

const store = createStore(reducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

 

이렇게 해주면 App포함 하위에 있는 컴포넌트는 store를 불러 쓸 수 있다.

 

참고로 reducer는 아까 만들어준 리듀서를 불러와주면 된다.

import reducer from './modules/todos';

 

참고로 modules의 파일들이 복수개가 될 수 있는데 그럴 때는 combineReducers라는 함수를 redux에서 불러와서 병합해주면 된다.

modules에 index.js를 만들고

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos,
});

export default rootReducer;

 

요런 식으로 해주면 된다.

이제 컨테이너에서 프로바이더가 공급해준 store를 가져올 타이밍이다.

 

const { todos } = useSelector((state) => ({
    todos: state.todos,
  }));

 

useSelector라는 함수를 사용해서 state를 가져왔다.

이게 useSelector라는 것이 Hooks인데 이 Hooks를 안 쓴다면 connect라는 걸 써서

connect( mapStateToProps, mapDispatchToProps )(Counter);

 

이렇게 해주어야 한다.

여기서 connect는... 쓸 일이 잘 없을 거 같다. 왜냐하면 useSelector가 너무 편하기 때문이다.

쨋튼 state를 가져왔으면 이제 todos 배열을 map을 해주자.

 

const todosMap = todos.map((todo) => (
    <li key={todo.id}>
      {todo.text}
      <button>삭제</button>
    </li>
  ));

 

이런 식으로 작성하고 이 todosMap이라는 배열을 컴포넌트로 넘겨줘서 {todosMap}이렇게 해주기만 하면 배열 랜더링은 끝난다.

 

자. 이제 추가 , 삭제를 구현해보자.

const dispatch = useDispatch();

 

useDispatch를 통해서 dispatch에 선언해 준다.

이 useDispatch도 Hooks인데 디스패치를 사용할 수 있게 해준다.

즉 아까 만들어놨던 액션 함수들을 

import { insert, remove, toggle } from '../modules/todos';

이렇게 불러와서,

const onInsert = (text) => dispatch(insert(text));
  const onRemove = (id) => dispatch(remove(id));
  const onToggle = (id) => dispatch(toggle(id));

이런 식으로 사용이 가능하다는 것이다.

그러면 저 변수에 담은 것들을 아까 map 할 때에 넣어주면 된다.

const todosMap = todos.map((todo) => (
    <li key={todo.id} onClick={() => onToggle(todo.id)}>
      {todo.done ? <s>{todo.text}</s> : <>{todo.text}</>}
      <button onClick={() => onRemove(todo.id)}>삭제</button>
    </li>
  ));

 

이런 식으로 하면 기본적으로 todoList기능을 다 수행한다.

 

끝~

(중간에 빼먹은 내용이나 부족한 부분이 있으면 댓글 남겨주세요~)

728x90
Comments