React Redux TodoList -1

chu·2021년 3월 16일
0

이번에는 react, redux를 사용한 TodoApp을 만들었다.

CRUD의 연습을 위한 작업이었다.

C : Create (생성)
R : Read (읽기)
U : Update (수정)
D : Delete (삭제)

전문적인 느낌은 아니지만, 기능을 어떻게 구현했는지 하나하나 기록을 해보자.

참고 : 개인적으로 진행된 작업이라 부족한 부분이 있을 것.

client.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import App from './App';
import store from './store';

ReactDOM.render(
  // react에서 store를 접근하기 위한
  <Provider store={store}>
    <App />
  </Provider>,
  document.querySelector('#root'),
);

App.jsx

// 이 페이지는 딱히 설명할 건 없는 것 같다.
import React from 'react';

import TodoList from './components/Todolist';
import AddList from './components/AddList';

const App = () => {
  return (
    <>
      <AddList />
      <TodoList />
    </>
  );
};

export default App;

components/Todolist.jsx

import React from 'react';
import { useSelector } from 'react-redux';

import TodoItem from './TodoItem';

const TodoList = () => {
  // state 데이터 불러오기.
  const item = useSelector((state) => state.post);
	
  // item을 map으로 ToddItem에 props보내기.
  return (
    <ul>
      {item.map((item) => (
        <TodoItem key={item.id} item={item} />
      ))}
    </ul>
  );
};

export default TodoList;

components/TodoItem.jsx

import React, { useState, useCallback } from 'react';
import { useDispatch } from 'react-redux';

// action 불러오기.
import { REMOVE_TODO_LIST, CHANGE_TODO_LIST } from '../reducers/index';

const TodoItem = ({ item }) => {
  const dispatch = useDispatch();
  // 리스트 수정 시 state를 변경하여, 조건 렌더링
  const [mode, setMode] = useState(true);
  
  // 수정 시 input 초깃값
  // 이렇게 넣어주면, 받아온 데이터를 value값으로 사용되면서,
  // 데이터를 직접 수정 할 수 없기 때문에 state로 넣어준다.
  const [text, setText] = useState(item.title);

  const onChangeText = useCallback(
    (e) => {
      setText(e.target.value);
    },
    [text],
  );
	
  // mode 변경 -> false (수정 폼 렌더링)
  const onClickChange = useCallback(() => {
    setMode(false);
  }, []);
	
  // mode 변경 -> true (초기 화면 렌더링)
  const onCancleChange = useCallback(() => {
    setMode(true);
  }, []);

  // Todo 삭제
  const onRemove = useCallback((id) => {
    dispatch({
      type: REMOVE_TODO_LIST,
      payload: id,
    });
  }, []);

  // Todo 수정
  const onChange = useCallback((id, text) => {
    dispatch({
      type: CHANGE_TODO_LIST,
      payload: {
        id: id,
        title: text,
      },
    });
    setMode(true);
  }, []);

  return (
    <li>
      {mode ? (
        <div>
          <p style={{ color: item.done ? 'green' : 'black' }}>{item.title}</p>
	  // 삭제 시 해당 리스트 id를 파라미터로 보낼 시
	  // 아래처럼 화살표함수로 감싸준다.
          <button onClick={() => onRemove(item.id)}>삭제</button>
          <button onClick={onClickChange}>수정</button>
        </div>
      ) : (
        <div>
          <input value={text} onChange={onChangeText} />
          <button onClick={onCancleChange}>취소</button>
          <button onClick={() => onChange(item.id, text)}>수정완료</button>
        </div>
      )}
    </li>
  );
};

export default TodoItem;

mode에 따른 조건 렌더링

mode true일 경우 : 초기 화면

mode false일 경우 : 수정 화면

위 화면처럼 수정 할 수 있는 input에 해당되는 리스트의 내용이
value값에 들어가 있다.

components/AddList.jsx

import React, { useState, useCallback } from 'react';
import { useDispatch } from 'react-redux';

// 이번 작업에서는 shortid 라이브러리를 사용하여, id를 만들었다.
import shortId from 'shortid';

// action 불러오기
import { ADD_TODO_LIST } from '../reducers/index';

const AddList = () => {
  const [value, setValue] = useState('');
  const dispatch = useDispatch();

  const onChangeInput = useCallback(
    (e) => {
      setValue(e.target.value);
    },
    [value],
  );

  const onAddList = useCallback(() => {
    if (value === '') { // 추가 시 input이 비어있을 경우
      return alert('내용을 입력해주세요.');
    }
    dispatch({
      type: ADD_TODO_LIST,
      payload: {
        id: shortId.generate(), // shortid 생성
        title: value, // 리스트 제목
        done: false, // 완료? 값
      },
    });
    setValue(''); // 리스트 추가 후 input 비어있도록 설정
  }, [value]);

  return (
    <div className="add-box">
      <input type="text" value={value} onChange={onChangeInput} />
      <button onClick={onAddList}>추가</button>
    </div>
  );
};

export default AddList;

reducers/index.jsx

import shortId from 'shortid';

// 불변성을 지키기 위해서 immer 라이브러리를 사용했다.
import produce from 'immer';

export const ADD_TODO_LIST = 'ADD_TODO_LIST';
export const REMOVE_TODO_LIST = 'REMOVE_TODO_LIST';
export const CHANGE_TODO_LIST = 'CHANGE_TODO_LIST';

// 초깃값 셋팅
const initialState = {
  post: [
    {
      id: shortId.generate(),
      title: '자바스크립트 공부하기',
      done: true,
    },
    {
      id: shortId.generate(),
      title: '리액트 공부하기',
      done: false,
    },
    {
      id: shortId.generate(),
      title: '노드 공부하기',
      done: false,
    },
  ],
};

// immer 사용 시 produce로 switch문을 감싸준다.
// 여기서는 state값을 바꾸는 것이 아닌 draft를 통해서 바꿔준다.
const todoReducer = (state = initialState, action) => {
  return produce(state, (draft) => {
    switch (action.type) {
      case ADD_TODO_LIST:
        draft.post.unshift(action.payload);
        break;
      case REMOVE_TODO_LIST:
        draft.post = draft.post.filter((v) => v.id !== action.payload);
        break;
      case CHANGE_TODO_LIST:
        // 리스트 id를 조회하여 post에 넣기.
        const post = draft.post.find((v) => v.id === action.payload.id);
        // 조회된 객체의 title을 변경
        post.title = action.payload.title;
        break;
      default:
        return state;
    }
  });
};

export default todoReducer;

store.jsx

import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

import reducer from './reducers/index';

const store = createStore(reducer, composeWithDevTools());

export default store;

이렇게 해서 Todo App 코딩 작업은 1차 마무리했다.
이제 CSS(Sass)를 통해서 디자인을 해 볼 예정이다.

profile
한 걸음 한걸음 / 현재는 알고리즘 공부 중!

0개의 댓글