본문 바로가기

Javascript

React_Lv1. My Todo List 만들기

✅ 1. IP 주소를 입력하세요.

https://16-week-lv-1.vercel.app/

 

React App

 

16-week-lv-1.vercel.app

✅ 2. GitHub 주소를 입력하세요.

https://github.com/webcreastory/16_WEEK_LV1.git

 

✅ 3. JSX 문법이란 무엇일까요?

JSX(Javascript XML)은 React에서 사용되는 JS확장문법으로 JS코드 내에 HTML(또는 XML)과 유사한 구조를 작성할 수 있게 해줍니다. 이를 통해 UI를 더욱 직관적으로 작성할 수 있게 하는데 효과적 문법입니다.  

 

✅ 4. 애플리케이션의 상태 값들을 컴포넌트 간 어떤 방식으로 공유하셨나요?

애플리케이션의 상태 값들은 Todo 컴포넌트 내에서 useState를 사용하여 공유하였고, App 컴포넌트에서는 Todo 컴포넌트를 호출하여 렌더링하는 방식으로 공유하였습니다.

 

✅ 5. 기능 구현을 위해 불변성 유지가 필요한 부분이 있었다면 하나만 설명해 주세요.

 clickAddButtonHandler 함수 내에서 setUsers를 호출할 때, 

const clickAddButtonHandler = () => {
    const newUser = {
        id: users.length + 1,
        title: title,
        text: text,
        isDone: false,
    };
    setUsers([...users, newUser]); // 불변성 유지를 위해 spread 연산자를 사용하여 새로운 배열 생성

    setTitle('');
    setText('');
};

이 부분에서 setUsers([...users, newUser])를 통해 newUser가 추가된 배열을 생성하여 불변성을 유지하고 있습니다. 이것은 기존의 users 배열을 변경하지 않고 새로운 배열을 생성하여 불변성을 유지하기 위함입니다.

 

✅ 6. 반복되는 컴포넌트를 파악하고 재사용할 수 있는 컴포넌트로 분리해 보셨나요? 그렇다면 어떠한 이점이 있었나요?

User 컴포넌트가 반복되는 패턴이 있으며, 이를 재사용 가능한 컴포넌트로 분리하여 코드를 개선하기 위해 User 컴포넌트를 Todo 컴포넌트로부터 분리함으로써, 코드의 가독성을 높일 수 있습니다. 

 

✅ App.js

import React from 'react'; // React 라이브러리를 사용하기 위해서는 항상 React를 임포트해야 함
import './App.css'; // App.css 파일을 임포트하고 있음
import Todo from './components/Todo'; // Todo컴포넌트를 components폴더 안에 있는 Todo.jsx파일로부터 임포트하고 있음

// App이라는 함수형 컴포넌트 : React 애플리케이션의 진입점(entry point)
// 이 컴포넌트는 <Todo></Todo>라는 JSX를 반환 (Todo 컴포넌트를 렌더링하는 역할을 함)
function App() {
    return <Todo></Todo>;
}
// App 컴포넌트를 모듈의 기본으로 내보냄
export default App;
// App 컴포넌트는 Todo 컴포넌트를 렌더링하고, 
// 애플리케이션의 최상위 레벨에 위치하여 
// 각 컴포넌트들을 조합하고 화면에 보여주는 역할을 함

 

✅ components

✅ Todo.jsx

// React는 React애플리케이션을 구성하는 핵심 라이브러리
// React라이브러리에서 React와 useState를 가져옴.
// useState는 React컴포넌트에서 상태관리를 위한 Hook 중 하나
import React, { useState } from 'react'; 
// 현재 디렉토리에서 ./User경로로 지정된 파일에서 User컴포넌트를 가져옴
// 다른 파일에 정의된 User컴포넌트를 현재 파일에서 사용하겠다는 의미 
import User from './User';

// 메인 컴퍼넌트
// React 함수형 컴포넌트
// 함수형 컴포넌트 Todo를 선언(화살표 함수 문법)
const Todo = () => {  
    // 초기 상태 설정
    // useState 훅(Hook)을 사용하여 컴포넌트의 상태를 초기화
    // useState는 배열 형태의 반환값을 가지며 
    //--- 첫번째 원소 users(상태 변수의 이름)는 상태값 자체, 두번째 원소 setUsers는 해당 상태를 업데이트하는 함수 
    const [users, setUsers] = useState([
        // 초기 상태로 지정된 값은 배열
        // 각 원소는 하나의 Todo를 나타내는 객체
        // 객체에는 id(고유 식별자), title, text, isDine 속성
        { id: 1, title: '[과제] React Lv.1', text: 'My Todo List 제출', isDone: false },
        { id: 2, title: '[강의] 리액트 입문주차', text: '2회독_개발 블로그 정리', isDone: true },
    ]); // = 이 코드는 초기 화면에 렌더링될 때 users 상태가 초기화되고 해당 상태는 title과 text라는 2개의 Todo 객체를 가지고 있음
    // 이후에 이 상태를 업데이트하거나 활용하여 React 컴포넌트를 동적으로 변경할 수 있음  

    // React 함수형 컴포넌트에서 useState 훅을 사용하여 2개의 상태 변수를 선언
    // 첫번째 상태 변수인 title을 선언하고 초기값을 빈 문자열로 설정(title은 현재 상태의 값, setTitle 함수를 사용해 이 상태를 업데이트)
    // 두번째 상태 변수인 text을 선언하고 초기값을 빈 문자열로 설정(text는 현재 상태의 값, setText 함수를 사용해 이 상태를 업데이트)
    const [title, setTitle] = useState('');
    const [text, setText] = useState('');
    // = 이렇게 선언된 상태 변수들은 주로 사용자의 입력을 받거나, 컴포넌트 내에서의 상태를 관리하는 데 사용 

    // 제목 입력값 변경 핸들러
    // React 함수형 컴포넌트에서 사용되는 event 핸들러 함수
    // titleChangeHandler라는 이름의 '함수형 변수'를 선언
    // 주로 입력 필드에서 발생하는 event(일반적으로 입력 내용이 변경될 때 발생하는 'onChange' event)를 처리
    const titleChangeHandler = (event) => { // 화살표 함수 문법을 사용하여 이벤트 핸들러 함수 정의(이벤트(event) 객체를 받아들이는 매개변수)
        setTitle(event.target.value); // setTitle 함수 호출(React 컴포넌트에서 title 상태 업데이트) ->   
        // event.target.value 값을 setTitle 함수에 전달하여 컴포넌트의 title 상태 업데이트
    }; // event.target은 이벤트가 발생한 HTML 요소(주로 입력 필드)/.value는 해당 요소의 현재 값, 즉 사용자가 입력한 내용을 나타냄
    // = 주로 사용자가 입력 필드에 텍스트를 입력할 때마다 호출되어, 입력된 텍스트를 컴포넌트의 title 상태로 업데이트하는 역할
    // = 입력된 값을 상태로 관리하고, 필요에 따라 화면을 다시 렌더링하여 반영

    // 내용 입력값 변경 핸들러
    //textChangeHandler라는 이름의 함수형 변수를 선언 
    // 주로 입력 필드에서 발생하는 이벤트(일반적으로 입력 내용이 변경될 때 발생하는 'onChange' 이벤트) 처리
    const textChangeHandler = (event) => { // 화살표 함수 문법을 사용하여 이벤트 핸들러 함수 정의 
        // 매개변수 event는 이벤트 객체를 받아들이는 매개변수
        setText(event.target.value); // event.target.value는 이벤트가 발생한 HTML 요소(주로 입력 필드)의 현재 값, 
        // 즉 사용자가 입력한 내용을 나타냄(이 값을 setText 함수에 전달하여 컴포넌트의 text 상태를 업데이트)
    }; // 사용자가 입력 필드에 텍스트를 입력할 때마다 호출되어, 입력된 텍스트를 컴포넌트의 text 상태로 업데이트하는 역할

    // 버튼 컴포넌트(함수형 컴포넌트)
    // Button이라는 이름의 함수형 컴포넌트 선언
    // 이 컴포넌트는 주로 버튼 역할을 하며, 두 개의 속성(props)을 받아들임
    // clickAddButtonHandler: 이는 버튼이 클릭되었을 때 실행될 이벤트 핸들러 함수
    // children: 이는 버튼 내에 포함될 내용(일반적으로 텍스트 또는 다른 React 엘리먼트)
    const Button = ({ clickAddButtonHandler, children }) => {
        // 이 부분은 컴포넌트의 렌더링을 정의
        // <button> HTML 엘리먼트 반환
        // onClick={clickAddButtonHandler}는 버튼이 클릭되었을 때 실행될 이벤트 핸들러를 정의 
        // clickAddButtonHandler로 전달된 함수가 클릭 이벤트에 바인딩되어 해당 함수가 실행됨
        // {children}: 이는 버튼 내에 포함될 내용을 나타냄(버튼 내의 텍스트로 표시됨)
        return <button onClick={clickAddButtonHandler}>{children}</button>;
    }; // =  Button 컴포넌트를 정의하여, 버튼을 렌더링하고 클릭 이벤트에 대한 핸들러를 받아 처리할 수 있는 컴포넌트

    // <추가> 버튼 클릭시 카드 추가하기
    // clickAddButtonHandler 함수가 호출될 때마다, 새로운 사용자 정보를 만듦
    const clickAddButtonHandler = () => { 
        const newUser = { //  newUser 객체를 생성하는데, 이 객체에는 다음과 같은 속성 포함(id, title, text, isDone)
            id: users.length + 1, // users 배열의 길이에 1을 더한 값으로 설정
            title: title,  // title 변수에서 가져온 값으로 설정
            text: text,  // text 변수에서 가져온 값으로 설정
            isDone: false, // 새로운 사용자의 상태를 나타냄(아직 완료되지 않았다는 것을 의미)
        };
        // setUsers 함수를 호출하여 users 배열 업데이트
        setUsers([...users, newUser]); 
        // 기존 users 배열과 새로 생성된 newUser 객체를 합친 새로운 배열을 생성하여 업데이트

        // <추가> 버튼 클릭 후 입력값 초기화
        //  입력 필드의 값을 변경한 후에 해당 값을 초기화
        setTitle('');
        setText('');
    };

    // Working 섹션에서 <삭제> 버튼 클릭 핸들러
    // clickRemoveButtonHandler 함수는 id라는 매개변수를 받음 
    // 사용자가 지정한 id를 가진 항목을 삭제하기 위한 것
    const clickRemoveButtonHandler = (id) => {
        // filter 메서드를 사용하여 users 배열에서 조건을 만족하는 요소들만 새로운 배열인 newUsers에 포함
        // filter 함수는 각각의 user에 대해 주어진 조건을 확인하고, 
        // user.id가 함수에 전달된 id와 일치하지 않는 요소들만 선택하여 새로운 배열을 생성
        // id와 일치하지 않는 사용자만을 남기고 필터링
        const newUsers = users.filter((user) => user.id !== id);
        // setUsers 함수를 호출하여 상태(state)를 업데이트
        // newUsers 배열은 이전 users 배열에서 특정 id를 가진 사용자를 제외한 새로운 배열 
        // 새로운 사용자 목록으로 설정하여 이전 목록에서 해당 ID를 가진 사용자를 삭제
        setUsers(newUsers);
    };

    // <완료> 또는 <취소> 버튼 클릭시 상태 변경
    // toggleStatusFunction 함수는 id라는 매개변수를 받음
    const toggleStatusFunction = (id) => {
        // map 함수를 사용하여 users 배열의 각 요소를 새로운 배열인 updatedUsers로 변환
        // map() 함수는 배열의 각 요소에 대해 주어진 함수를 호출하고, 그 함수가 반환하는 결과를 모아서 새로운 배열을 생성
        // ----- 배열을 순회하면서 각 요소에 동일한 작업을 적용하여 새로운 배열을 만들 때 유용
        const updatedUsers = users.map((user) => {
            // 만약 user.id가 함수에 전달된 id와 일치한다면, 해당 사용자의 isDone 속성을 반전시켜서 변경
            if (user.id === id) {
                // 객체 전개 연산자(...)를 사용하여 객체를 복사하고, 해당 객체의 isDone 속성을 토글(반전)시킴
                // 반전 연산자 !를 사용하여 user.isDone의 값을 뒤집고, 해당 값을 가지고 새로운 객체를 생성하여 반환
                // 그렇지 않은 경우에는 기존의 사용자 정보를 그대로 반환
                return { ...user, isDone: !user.isDone };
            }
            return user;
        }); //setUsers 함수를 호출하여 상태(state)를 업데이트
        setUsers(updatedUsers); 
        // updatedUsers 배열은 이전 users 배열을 기반으로  
        // --- 특정 id를 가진 사용자의 isDone 값을 토글(반전)시킨 새로운 배열
    };

    // Done 섹션에서 <삭제> 버튼 클릭 핸들러
    // clickRemoveDoneButtonHandler 함수는 id라는 매개변수를 받음
    // 특정 조건을 충족하는 사용자(isDone이 true인 사용자)를 제외하고 새로운 배열을 만듦
    const clickRemoveDoneButtonHandler = (id) => {
        // filter 함수를 사용하여 users 배열에서 조건을 만족하는 요소들만 새로운 배열인 newUsers에 포함시킴
        const newUsers = users.filter((user) => user.id !== id);
        // newUsers 배열은 이전 users 배열에서 특정 id를 가진 사용자를 제외한 새로운 배열
        // = id에 해당하는 사용자를 제거하고, 해당 사용자가 완료된 작업을 나타내는 isDone이 true인 경우에만 동작
        setUsers(newUsers);
    };

    // JSX(JavaScript XML, React에서 UI를 작성하기 위해 사용되는 JS 확장 문법)로 작성된 코드
    // 1. Todo List 구성 요소: 
    // clickRemoveDoneButtonHandler, clickAddButtonHandler, titleChangeHandler, textChangeHandler, 
    // toggleStatusFunction 함수들과 함께 <Button>과 <User> 컴포넌트를 사용하여 Todo List를 구성

    // 2. 화면 레이아웃 구성:
    // home-background, home-container, header, input-box, Working-body 등의 CSS 클래스를 사용하여 UI 스타일링
    // 헤더에는 My Todo List와 React라는 두 개의 텍스트 구성

    // 3. 두 부분으로 나누어진 Todo 리스트:
    // Working 부분은 isDone이 false인 항목들을 보여주고 있고, 
    // Done 부분은 isDone이 true인 항목들을 보여줌
    // 두 부분에서 각각 filter 함수를 사용하여 users 배열을 필터링하고, 해당하는 부분에 맞는 항목들만을 보여줌
    // map 함수를 사용하여 각각의 Todo 항목을 User컴포넌트로 변환하고, 해당 컴포넌트들을 UI에 렌더링 함

    return (
        <div className="home-background">
            <div className="home-container">
            
                <div className="header">
                    <span>My Todo List</span>
                    <span>React</span>
                </div>

                <div className="input-box">
                    제목 &nbsp;
                    <input value={title} onChange={titleChangeHandler} />
                    내용 &nbsp;
                    <input value={text} onChange={textChangeHandler} />
                    <Button clickAddButtonHandler={clickAddButtonHandler}>추가하기</Button>
                </div>

                <h1>🔥 Working</h1>
                <div className="Working-body">
                    {users
                        .filter((item) => !item.isDone)
                        .map(function (item) {
                            return (
                                <User
                                    key={item.id}
                                    item={item}
                                    removefunction={clickRemoveButtonHandler}
                                    toggleStatusFunction={toggleStatusFunction}
                                />
                            );
                        })}
                </div>

                <h1>👍 Done</h1>
                <div className="Working-body">
                    {users
                        .filter((item) => item.isDone)
                        .map(function (item) {
                            return (
                                <User
                                    key={item.id}
                                    item={item}
                                    removefunction={clickRemoveDoneButtonHandler}
                                    toggleStatusFunction={toggleStatusFunction}
                                />
                            );
                        })}
                </div>
            </div>
        </div>
    );
};

export default Todo;
// Todo라는 컴포넌트를 내보내고 다른 파일에서 가져와서 사용

✅ User.jsx

// React 라이브러리를 현재 파일에서 사용하겠다고 선언
import React from 'react';

// User라는 함수형 컴포넌트
// ES6의 비구조화 할당을 사용하여 컴포넌트의 props를 바로 받아옴
// props = item, removefunction, toggleStatusFunction
const User = ({ item, removefunction, toggleStatusFunction }) => {
    return (
        // React에서 리스트를 렌더링할 때 각 요소에 고유한 key를 지정해야 함
        // item.id를 키로 사용
        // item.isDone이 true인 경우에만 done 클래스가 추가됨(완료된 항목은 스타일링을 위해 done 클래스를 받음)
        // item 객체에 있는 title과 text 값을 출력
        <div key={item.id} className={`todo-container ${item.isDone ? 'done' : ''}`}>
            {item.title}
            <br /> 
            {item.text}
            <br />
            <div className="button-container">
                <button onClick={() => removefunction(item.id)}>삭제하기</button>
                <button onClick={() => toggleStatusFunction(item.id)}>{item.isDone ? '취소' : '완료'}</button>
            </div>
        </div> // 버튼을 클릭하면 removefunction이 호출되고, 해당 item의 id가 전달 -> 이를 통해 해당 아이템이 삭제되는 함수를 실행
    ); // 버튼 텍스트를 item.isDone의 상태에 따라 다르게 표시
       // item.isDone이 true라면 '취소'라는 텍스트가 보이며, 클릭 시 toggleStatusFunction이 호출
};     // item.isDone이 false라면 '완료'라는 텍스트가 보이고, 클릭 시 toggleStatusFunction이 호출

export default User;
// User 컴포넌트는 각 Todo 항목을 렌더링하고, 해당 항목의 삭제와 상태 변경을 담당하는 버튼을 보여줌
// 설정된 item prop으로부터 해당 항목의 정보를 받아와 UI로 표시하고, 
// removefunction과 toggleStatusFunction을 호출하여 해당 항목의 삭제 및 상태 변경을 처리

✅ User.css

.home-background {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  /* 배경 이미지가 컨텐츠 위에 나타나도록 설정 */

  background-image: linear-gradient(45deg,
      rgb(51 43 43 / 75%),
      rgb(20 19 20 / 61%)), url("https://miro.medium.com/v2/resize:fit:1400/1*QDQvlCg420lzRElCK4AYhw.png");
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

.home-container {
  width: 1200px;
  margin: 0px auto 0px auto;
}

.header {
  height: 50px;
  width: 1100px;

  padding: 10px;
  margin-bottom: 10px;

  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;

  font-size: 50px;
  font-weight: 700;
  color: rgb(18, 78, 89);
}

.input-box {
  background-color: rgba(44, 40, 40, 0.921);
  border-radius: 6px;

  height: 100px;
  width: 1100px;

  padding-left: 30px;
  padding-right: 30px;

  color: white;
  font-size: 25px;
  font-weight: 800;

  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

.input-box>input {
  height: 40px;
  width: 280px;

  margin-right: auto;
  padding-left: 10px;

  border-radius: 7px;
  border: none;

  font-size: 18px;
  font-weight: 600;
}

.input-box>Button {
  width: 180px;
  height: 45;
  padding: 10px;

  background-color: rgb(71, 163, 182);

  border: none;
  border-radius: 5px;

  color: white;
  font-size: 20px;
  font-weight: 800;
}

.input-box>Button:hover {
  width: 180px;
  height: 45;
  padding: 10px;

  border: none;
  border-radius: 5px;

  font-size: 22px;
  font-weight: 900;
  color: white;
  background-color: red;
}

h1 {
  margin-right: 1000px;
  margin-bottom: 15px;

  font-size: 30px;
  font-weight: 800;
  color: white;
}

.Working-body {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;

  padding-left: 30px;
}

.todo-container {
  width: 350px;
  height: 200px;
  border: 5px solid white;
  border-radius: 10px;

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  font-size: 20px;
  font-weight: 800;
  color: white;
}

.button-container>Button {
  width: 100px;
  height: 40;
  padding: 10px;
  margin: 10px;

  background-color: red;

  border: none;
  border-radius: 5px;

  color: white;
  font-size: 15px;
  font-weight: 700;
}

.button-container>Button:hover {
  width: 100px;
  height: 40;
  padding: 10px;
  margin: 10px;

  background-color: black;

  border: none;
  border-radius: 5px;

  color: white;
  font-size: 16px;
  font-weight: 700;
}

'Javascript' 카테고리의 다른 글

DOM-API 실습  (1) 2023.12.19
DOM:Document object Model 기본 개념  (0) 2023.12.19
콜백함수 제너레이터(Generator)  (0) 2023.12.19
비동기 작업의 동기적 표현 - Promise  (0) 2023.12.19
콜백함수_promise  (1) 2023.12.18