끄적끄적 코딩
article thumbnail
Published 2023. 4. 25. 01:56
[React] ToDo List 만들기 React

create-react-app을 사용해서 프로젝트를 생성합니다.

npx create-react-app todo-app


sass를 사용해서 스타일을 꾸며 주도록 하겠습니다.

npm add sass classnames react-icons

classnames는 조건부 스타일링을 좀 더 편하게 하기 위함
react-icons는 리액트에서 다양하고 예쁜 아이콘을 사용할 수 있는 라이브러리
=> https://react-icons.netlify.com/ 

 

React Icons

React Icons Include popular icons in your React projects easily with react-icons, which utilizes ES6 imports that allows you to include only the icons that your project is using. Installation (for standard modern project) npm install react-icons --save Usa

react-icons.github.io

 

npm start

생성된 create-react-app을 실행해 볼 수 있습니다.


컴포넌트

TodoTemplate : 화면을 가운데에 정렬시켜 주며, 앱 타이틀(일정 관리)을 보여줍니다. children으로 내부 JSX를 props로 받아 와서 렌더링해 줍니다.

TodoInsert : 새로운 항목을 입력하고 추가할 수 있는 컴포넌트입니다. state를 통해 인풋의 상태를 관리합니다.

TodoListItem : 각 할 일 항목에 대한 정보를 보여 주는 컴포넌트입니다. todo 객체를 props로 받아 와서 상태에 따라 다른 스타일의 UI를 보여줍니다.

TodoList : todos 배열을 props로 받아 온 후, 이를 배열 내장 함수 map을 사용해서 여러 개의 TodoListItem 컴포넌트로 변환하여 보여 줍니다.

App.js

// App.js
import { useState, useRef, useCallback } from "react";
import TodoTemplate from "./components/TodoTemplate";
import TodoInsert from "./components/TodoInsert";
import TodoList from "./components/TodoList";

const App = () => {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: "리액트의 기초 알아보기",
      checked: true,
    },
    {
      id: 2,
      text: "컴포넌트 스타일링해 보기",
      checked: true,
    },
    {
      id: 3,
      text: "일정 관리 앱 만들어 보기",
      checked: false,
    },
  ]);

  const nextId = useRef(4);

  const onInsert = useCallback(
    (text) => {
      const todo = {
        id: nextId.current,
        text,
        checked: false,
      };
      setTodos(todos.concat(todo));
      nextId.current += 1;
    },
    [todos]
  );

  const onRemove = useCallback(
    (id) => {
      setTodos(todos.filter((todo) => todo.id !== id));
    },
    [todos]
  );

  const onToggle = useCallback(
    (id) => {
      setTodos(todos.map((todo) => (todo.id === id ? { ...todo, checked: !todo.checked } : todo)));
    },
    [todos]
  );

  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </TodoTemplate>
  );
};

export default App;

useState를 사용해서 todos의 상태를 정의합니다.

nextId는 todos에 새로운 값이 추가될 때 id가 겹치지 않게 넣어주기 위해서 사용합니다.
id 값은 렌더링 되는 정보가 아니므로 useRef를 사용하여 관리합니다.

onInsert 함수는 입력된 text로 todo 객체를 생성합니다. setTodos를 사용해 todos의 상태를 변경합니다. concat함수를 사용해서 추가된 todo를 todos에 붙여줍니다.
todos가 변경 될 때만 함수를 재생성하게 useCallback을 사용해주었습니다.
* props로 전달해야 할 함수를 만들 때 useCallback을 사용하여 함수를 감싸는 것을 습관화 하세요.
해당 함수를 TodoInsert 컴포넌트의 props로 설정해주었습니다.

onRemove 함수는 filter함수를 사용하여 App 컴포넌트에서 id를 파라미터로 받아 와서 같은 id를 가진 항목을 todos 배열에서 지워 줍니다.
todos가 변경 될 때 함수를 재생성하게 useCallback을 사용해주었습니다.
해당 함수를 TodoList 컴포넌트의 props로 설정해주었습니다.

onToggle 함수는 id가 동일한 경우 해당 값의 checked값을 not연산 해줍니다. (true -> false, false -> true)
todos가 변경 될 때 함수를 재생성하게 useCallback을 사용해주었습니다.
해당 함수를 TodoList 컴포넌트의 props로 설정해주었습니다.


TodoTemplate.js

// TodoTemplate.js
import "./scss/TodoTemplate.scss";

const TodoTemplate = ({ children }) => {
  return (
    <div className="TodoTemplate">
      <div className="app-title">일정 관리</div>
      <div className="content">{children}</div>
    </div>
  );
};

export default TodoTemplate;

TodoTemplate.js는 Todo의 전체적인 틀을 만들어주는 컴포넌트입니다.

TodoTemplate의 children을 <div className="content">안에 넣어주었습니다.

TodoInsert.js

// TodoInsert.js
import { useState, useCallback } from "react";
import { MdAdd } from "react-icons/md";
import "./scss/TodoInsert.scss";

const TodoInsert = ({ onInsert }) => {
  const [value, setValue] = useState("");

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

  const onSubmit = useCallback(
    (e) => {
      onInsert(value);
      setValue("");

      e.preventDefault();
    },
    [onInsert, value]
  );

  return (
    <form className="TodoInsert" onSubmit={onSubmit}>
      <input placeholder="할 일을 입력하세요" value={value} onChange={onChange} />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};

export default TodoInsert;

TodoInsert.js는 입력받는 부분과 해당 기능에 대해서 구현되어 있는 컴포넌트입니다.

useState를 사용해서 value의 상태를 정의합니다.

onChange함수는 input값을 변경하기 위해서 사용합니다.
함수는 바뀌지 않으며, 재사용할 수 있으므로 mount될 때 한번만 만들어지도록 useCallback을 사용해주었습니다.

onSubmit함수가 실행되면 onInsert함수를 실행하고 value의 상태를 ""로 만들어줍니다.
e.preventDefault()를 사용해 화면이 넘어가지 않도록 해줍니다.
함수내에서 onInsert함수와 value를 사용하므로 해당 값이 바뀔 때만 새로 생성하게 해주었습니다.


TodoList.js

// TodoList.js
import TodoListItem from "./TodoListItem";
import "./scss/TodoList.scss";

const TodoList = ({ todos, onRemove, onToggle }) => {
  return (
    <div className="TodoList">
      {todos.map((todo) => (
        <TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToggle={onToggle} />
      ))}
    </div>
  );
};

export default TodoList;

TodoList는 할일 목록을 보여주는 컴포넌트입니다.

props로 todos와 onRemove, onToggle을 받습니다.

todos는 '할일'들을 가지고 있습니다. map을 사용해서 todos안의 객체 수만큼 TodoListItem을 만들어줍니다.


TodoListItem.js

// TodoListItem.js
import { MdCheckBoxOutlineBlank, MdCheckBox, MdRemoveCircleOutline } from "react-icons/md";
import cn from "classnames";
import "./scss/TodoListItem.scss";

const TodoListItem = ({ todo, onRemove, onToggle }) => {
  const { id, text, checked } = todo;
  return (
    <div className="TodoListItem">
      <div className={cn("checkbox", { checked })} onClick={() => onToggle(id)}>
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;

TodoListItem.js는 할일을 나타내는 컴포넌트입니다.

props로 받은 todo로 할일을 나타내고 onRemove와 onToggle함수를 사용해서 기능을 추가해주었습니다.


scss

// TodoInsert.scss
.TodoInsert {
  display: flex;
  background: #495057;
  input {
    background: none;
    outline: none;
    border: none;
    padding: 0.5rem;
    font-size: 1.125rem;
    line-height: 1.5;
    color: white;
    &::placeholder {
      color: #dee2e6;
    }
    flex: 1;
  }
  button {
    background: none;
    outline: none;
    border: none;
    background: #868e96;
    color: white;
    padding-left: 1rem;
    padding-right: 1rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    cursor: pointer;
    transition: 0.1s background ease-in;
    &:hover {
      background: #adb5bd;
    }
  }
}

 

// TodoList.scss
.TodoList {
  min-height: 320px;
  max-height: 513px;
  overflow-y: auto;
}
// TodoListItem.scss
.TodoListItem {
  padding: 1rem;
  display: flex;
  align-items: center;
  &:nth-child(even) {
    background: #f8f9fa;
  }
  .checkbox {
    cursor: pointer;
    flex: 1;
    display: flex;
    align-items: center;
    svg {
      font-size: 1.5rem;
    }
    .text {
      margin-left: 0.5rem;
      flex: 1;
    }
    &.checked {
      svg {
        color: #22b8cf;
      }
      .text {
        color: #adb5bd;
        text-decoration: line-through;
      }
    }
  }
  .remove {
    display: flex;
    align-items: center;
    font-size: 1.5rem;
    color: #ff6b6b;
    cursor: pointer;
    &:hover {
      color: #ff8787;
    }
  }

  & + & {
    border-top: 1px solid #dee2e6;
  }
}
// TodoTemplate.scss
.TodoTemplate {
  width: 512px;
  margin-left: auto;
  margin-right: auto;
  margin-top: 6rem;
  border-radius: 4px;
  overflow: hidden;

  .app-title {
    background: #22b8cf;
    color: white;
    height: 4rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .content {
    background: white;
  }
}

& 상위 선택자
https://webclub.tistory.com/396

 

LESS Parent Selector(&)

LESS 부모 선택자 이 글에서는 LESS 문법 중 부모(상위) 선택자에 대해 알아보는 시간을 가져보겠습니다. LESS Parent Selectors documentation Parent Selectors LESS 에서 부모 선택자를 참조하기위해 & 지시자를

webclub.tistory.com

 

결과 화면

 

 

'React' 카테고리의 다른 글

[React] 리덕스 미들웨어  (0) 2023.05.30
[React] 리액트 라우터  (1) 2023.05.02
[React] JSX  (0) 2023.04.18
[React] props, defaultProps, propTypes  (0) 2021.03.06
[React] React Native  (0) 2021.02.09

검색 태그