노마드코더

[ReactJS로 영화 웹 서비스 만들기] ToDo List 만들기

졸려질려 2023. 6. 9. 20:31
반응형

 노마드코더의 "ReactJS로 영화 웹 서비스 만들기" 강의에서 ToDoList 를 만들어보았다. 강의에서는 단순히 <input> 에 입력하면 아래에 목록으로 출력이 되는 간단한 형식이었다. 그렇게 넘어가려던 중, 강의 댓글을 보니 ToDoList 에 삭제 기능을 넣는 것에 대한 논의가 이뤄지는 것을 발견했다. 그래서 필자도 삭제 기능까지 포함하여 꽤 있어보이는 ToDoList 를 만들어 보기로 했다.


1. 강의 결과물

 강의에서 배운 결과물의 코드와 화면은 아래와 같다.

import {useState } from "react";

function App() {
  const [toDo, setToDo] = useState("");
  const [toDosList, setToDosList] = useState([]);

  const onChange = (event) => {
    setToDo(event.target.value);
  };
  const onSubmit = (event) => {
    event.preventDefault();
    console.log(toDo);

    setToDosList((prevArray) => [...prevArray, toDo]);
    setToDo("");
  }
  
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input value={toDo} onChange={onChange} type="text" placeholder="Write your to do..." />
        <button>Add To Do</button>
      </form>
      <hr />
      <ul>
        {toDosList.map((item, index) => {
          return <li key={index}>{item}</li>
        }
        )}
      </ul>
    </div>
  );
}

export default App;

 이제 간단히 배운 ToDoList 를 아는 지식들을 최대한 활용하여 바꿔보기로 한다.


2. UI 변경

 ToDoList 를 한 번 꾸며봐야겠다고 생각이 들었을 때, 바로 생각이 난 것은 "Momentum" 이라는 확장 프로그램이었다. "Momentum" 은 크롬 페이지를 꾸며주는 확장 프로그램인데, 노마드코더의 Vanilla JS 수업에서 클론코딩 했던 소재이기도 하다.

Momentum

 위 사진에 보이는 것처럼 그대로 한다기 보다는 시계, 문구 등의 배치를 참고해보려 한다. 그리고 배경도 바꿔보기로 한다.

 필자가 만든 결과물을 미리 첨부하자면 다음과 같다.

필자가 실습 해본 ReactJS ToDoList


3. 작업 과정

 작업 과정을 기술하기에 앞서, 필자는 본 실습을 하기 위해 "ToDoList.js" 파일과 "ToDoList.module.css" 파일을 새로 생성하였다. "ToDoList.js" 파일에는 이전에 배운 ToDoList 코드가 그대로 복사되어있다. 이제 그 위에 "ToDoList.js" 파일 안에서 수정해 나간다.

1) ToDo 삭제 로직 추가

 우선, 제일 중요한 것은 "삭제 기능" 을 추가하는 것이다. 디자인이야 어떤 모습이든, 기능이 정상적으로 동작하는 것이 제일 먼저이기 때문이다. State 에 저장되어 있는 ToDo 목록에서 선택한 아이템을 삭제하는 함수를 따로 만들었다.

// ToDoList.js

...
  const onDeleteToDo = (index) => {
    const currentArray = [...toDosList];
    currentArray.splice(index, 1);
    
    setToDosList(currentArray);
  };
...

 ToDo 목록의 각 아이템에는 index 값이 포함되어 있기 때문에, index 값으로 splice() 함수를 사용하여 리스트에서 선택한 아이템을 삭제하도록 구현하였다. 위 코드에서 조금 의문이 드는 점이 있다면, 그건 아마 State 의 값을 복사하는 과정일 것이다. State 에서 제공해주는 Modifier 함수는 파라미터로 현재의 State 값을 전달해준다. 그렇다면 복사하는 과정 없이 Modifier 내부에서 바로 하는 것이 좋지 않을까 생각이 들것이다.

// ToDoList.js

...
  const onDeleteToDo = (index) => {
    setToDosList((currentArray) => {
    	currentArray.splice(index, 1);
    });
  };
...

 하지만, 위와 같이 하게 되면 오히려 선택한 아이템만 남아버리는 결과를 보게 된다. 그 이유는, splice() 함수의 반환값은 "삭제한 아이템 값들" 이기 때문이다. 즉, 위와 같이 코드를 구성하면, 아이템을 선택했을 때 해당 아이템만 목록에서 남게 된다. 따라서, 현재의 State 에 있는 toDosList 의 배열의 내부 아이템들을 그대로 복사하여, 수정과정을 거친 후에 새롭게 넣어준 것이다.

 이 외에도 필자의 욕심에는 수정 버튼까지 추가하고 싶었으나, 너무 많은 시간을 투자할 것 같아서 초기 목표만 달성하기로 하였다. 사실 로직은 "삭제 기능" 을 추가한 것으로 목표를 이뤘다. 이제 디자인을 수정한다.


2) 배경 이미지 바꾸기

 필자는 은하수 사진을 좋아해서 은하수가 걸쳐진 사진을 구했다. 출처 링크는 다음과 같다.

 

배경 화면 : 1920x1080px, 은하수, 공간 1920x1080 - wallhaven - 669663 - 배경 화면 - WallHere

무료 고해상도 사진 1920x1080px, 은하수, 공간 @wallhaven, 촬영 알 수없는 카메라 10/31 2017 함께 찍은 사진

wallhere.com

 이제 ToDoList 컴포넌트의 최상위에 있는 <div> 에 Style 을 적용해준다. Module CSS 파일에는 다음과 같이 추가한다.

// ToDoList.module.css

.rootContainer {
    /* For Background */
    width: 100vw;
    height: 100vh;
    background-image: url("../assets/testPicture.jpg");
    background-repeat: no-repeat;
    
    /* For Child Elements */
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

 주석에 표시한 것처럼 위 4가지 속성들이 배경을 위해 설정한 부분들이다. 아래 4가지는 자식 요소들에 적용할 부분들이다. 이제 JS 파일에도 위 스타일을 적용시켜준다.

// ToDoList.js
import styles from "./ToDoList.module.css";

...
  return (
    <div className={styles.rootContainer}>
      ...
    </div>
  );
...

그런데, 이대로 웹페이지를 리로드 해보면 조금 불편한 결과를 보게된다.

 바로 상하좌우로 약간의 마진? 패딩? 같은 부분이 생기는 것이다. 깔끔하게 전체를 이미지로 덮고 싶은 사람에게는 제일 불편한 결과일 것이다. 이 점은 CSS 파일에서 <body> 에 margin 과 padding 을 모두 0으로 설정하면 해결할 수 있다. 따라서, 배경을 빈틈없이 꽉 채우고 싶다면 다음과 같이 CSS 코드를 추가하면 된다.

// ToDoList.module.css

.rootContainer {
    width: 100vw;
    height: 100vh;
    background-image: url("../assets/testPicture.jpg");
    background-repeat: no-repeat;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

body {
    margin: 0;
    padding: 0;
}

3) 디지털 시계 추가

 멋진 배경에 ToDo 입력만 있으면 뭔가 허전한 것 같아, 시계를 추가해보기로 했다. 필자 나름대로 만들어본 후에, 더 좋은 코드가 있을까 하여 Github 에 올라온 특정 ReactJS Clock 의 코드를 참고하여 다음과 같이 구현하였다. TimeText 라는 이름으로 컴포넌트를 따로 만들었다.

// ToDoList.js

...
function TimeText() {
    const [seconds, setSeconds] = useState(0);
    const [minutes, setMinutes] = useState(0);
    const [hours, setHours] = useState(0);

    useEffect(() => {
        const update = () => {
            const today = new Date();
            setSeconds(today.getSeconds());
            setMinutes(today.getMinutes());
            setHours(today.getHours());
        }

        update();

        const timeFlow = setInterval(() => {
            update();
        }, 1000);

        return () => clearInterval(timeFlow);
    }, []);

    return (
      <div className={styles.timeStyle}>
        {hours.toString().padStart(2, "0")} :{" "}
        {minutes.toString().padStart(2, "0")} :{" "}
        {seconds.toString().padStart(2, "0")}
      </div>
    );
}
...

 시계에 필요한 시(Hour), 분(Minute), 초(Second) 의 값을 각각 State 로 설정하고, 초기값은 useEffect 를 통해 초기화 되도록 하였다. 만약 useEffect 를 사용하지 않는다면, setInterval 과 State 로 인한 컴포넌트 리렌더링으로 인해 시계가 정상적으로 작동하지 않을 것이다. 따라서, 최초 렌더링에서만 시계의 초기값을 설정하기 위해 useEffect 를 사용하였다. 그리고, TimeText 컴포넌트가 사라지면, setInterval 을 리셋하기 위해 return 을 사용하여 clearInterval 을 해주었다.

 추가로, padStart() 는 시계에서 숫자가 1자리 수가 될 경우에, 어색해 보일 것을 염려하여 2자리 수가 유지되도록 하기 위해 추가하였다. 마지막으로, TimeText 컴포넌트에 적용한 styles.timeStyle 은 다음과 같다.

// ToDoList.module.css

...
.timeStyle {
    font-size: 7vw;
    color: white;
    font-weight: 600;
    margin: 20px;
}
...

4) ToDoItem 컴포넌트 추가

 이제 ToDo List 를 추가하고, 목록으로 나열하는 부분을 어떻게 구현했는지 기술한다. 삭제 기능을 추가하기 위해 버튼이 목록의 아이템마다 필요하겠다는 생각이 든 순간, <li> 로 구현하기 보다는 ToDoList 의 각 아이템에 적합한 새로운 컴포넌트를 추가하는 것이 좋겠다고 판단했다. 그래서 ToDoItem 이라는 이름의 컴포넌트를 추가하였다.

// ToDoList.js

...
function ToDoItem({ text, onDeleteClicked }) {
  return (
    <div className={styles.toDoItemStyle}>
      <label>
        <input className={styles.checkboxStyle} type="checkbox" defaultChecked={false} />
        {text}
      </label>
      <button className={styles.deleteButtonStyle} onClick={onDeleteClicked}></button>
    </div>
  );
}
...

 ToDoItem 컴포넌트는 Props 로 "text" 와 "onDeleteClicked" 를 받는다. "text" 는 <input> 을 통해 입력한 ToDo 값이고, "onDeleteClicked" 는 삭제 버튼을 클릭했을 때 콜백할 함수이다. ToDoItem 컴포넌트의 <div> 에 적용한 스타일 코드는 다음과 같다.

// ToDoList.module.css

.toDoItemStyle {
    color: white;
    font-size: 1.5vw;
    margin: 10px 10px 0px 10px;
    display: flex;
    align-items: center;
}

 margin 을 통해 ToDoItem 컴포넌트 간에 간격을 주었다. display 와 align-items 는 ToDoItem 컴포넌트에 사용되는 체크박스, 텍스트, 삭제 버튼을 Vertical Center 로 정렬되도록 하기 위해 추가했다.

margin 의 값을 4개로 설정한다면, Top, Right, Bottom, Left 순으로 설정된다
// ToDoList.module.css

.checkboxStyle {
    transform: scale(2);
    margin: 0 15px 0 0;
}

 checkboxStyle 은 텍스트와 간격을 조정하고, 체크박스의 크기를 조절하기 위해 추가하였다. transform 부분이 체크박스의 크기를 조정하는 설정값이다.

// ToDoList.module.css

.deleteButtonStyle {
    background-image: url("../assets/delete.png");
    background-size: cover;
    border-width: 0;
    background-color: transparent;
    background-size: 100%;
    width: 30px;
    height: 30px;
    margin: 0 0 0 15px;
}

 삭제 버튼에 대한 스타일은 위와 같다. 간단하게 "삭제" 텍스트만 추가해서 마무리 지을 수 있었지만, 뭔가 이미지 버튼으로 만들고 싶은 욕심이 생겼다. 사실 ToDoItem 컴포넌트 내에서 맨 오른쪽에 배치를 시켜주고 싶었지만, 여러번 시도에 실패하고, 텍스트 부분에 글자 수 제한도 추가해야할 것 같아서 급하게 마무리 지었다. 삭제 버튼의 이미지 출처는 다음 링크이다.


5) ToDo 입력창 꾸미기

 마지막으로 ToDo 의 입력창(<input>) 을 꾸민 것에 대해 기술한다. 사실 이 부분은 CSS 만 추가되었고, JS 코드는 강의에서 따라한 코드와 같다.

// ToDoList.js

...
    <form onSubmit={onSubmit}>
      <input
        className={styles.toDoInputStyle}
        value={toDo}
        onChange={onChange}
        type="text"
        placeholder="Write your to do..."
      />
    </form>
...

 위 코드에서 적용한 toDoInputStyle 의 CSS 코드와 적용 결과는 다음과 같다.

// ToDoList.module.css

.toDoInputStyle {
    width: 30vw;
    outline: 0; /* 입력창에 포커스가 잡혔을 때, 테두리 투명화 */
    border-width: 0 0 3px; /* 아래쪽 border만 3px 굵기로 설정 */
    border-color: white;
    font-size: 2vw;
    background-color: transparent;
    caret-color: white; /* 입력 커서 색상 */
    text-align: center;
    color: white;
}

::placeholder {
    color: white;
}

toDoInputStyle 적용 결과


4. 최종 결과물

 "완벽해" 라는 생각이 들 정도는 아니지만, 나중에 기회가 된다면 더 발전시켜서 마무리를 짓고 싶다. 원하는 개선점은 다음과 같다.

  • 삭제 버튼의 위치를 목록 아이템에서 맨 우측에 고정
  • 목록 아이템의 텍스트가 일정 이상으로 길면 MARQUEE 효과나 텍스트 끝에 생략 효과 부여
  • 체크 박스를 체크하면 텍스트에 취소선이 적용되어 할 일을 마쳤다는 효과 부여
  • 할 일 목록을 브라우저의 로컬 저장소에 저장

 우선 강의를 다 듣는 것이 우선이기 때문에, 본 실습은 여기에서 마친다.

반응형