상세 컨텐츠

본문 제목

[React.js 1] p2. 할 일 관리 앱 만들기

23-24/React.js 1

by ssxb 2023. 12. 1. 10:00

본문

728x90

 

프로젝트 준비하기

요구사항 분석하기

[할 일 관리] 앱의 최종 구현 모습

 

[할 일 관리] 앱의 기능들

  • 오늘의 날짜를 요일, 월, 일, 연도순으로 표시합니다.
  • 할일 (Todo)을 작성하는 입력 폼이 있고 <추가> 버튼 클릭시 할 일 아이템 생성합니다.
  • 생성한 아이템을 페이지 하단에 리스트로 표시하며 키워드 검색으로 원하는 할 일만 추출할 수 있습니다.
  • 리스트로 표시하는 낱낱의 할 일 아이템은 일을 마쳤는지 여부를 표시하는 체크박스, 아이템 이름, 등록 날짜, <삭제> 버튼으로 이루어져 있습니다.

 

컴포넌트로 본 [할 일 관리] 앱

 

[할 일 관리] 앱의 UI 요소를 컴포넌트 단위로 나누기

  • Header :오늘의 날짜를 표시 형식에 맞게 보여 줍니다.
  • TodoEditor : 새로운 할 일 아이템을 등록
  • TodoList : 검색어에 맞게 필터링된 할 일 리스트를 렌더링하며 검색 폼이 공백이면 필터링하지 않습니다.
  • TodoItem : 체크박스를 클릭하면 할 일을 마쳤는지 여부가 토글되고, <삭제> 버튼을 클릭하면 해당 아이템을 삭제합니다.

+ 토글 : 디지털 신호가 1 또는 0을 되풀이하는 상태라는 뜻 → 이 앱에서는 체크 표시 여부에 따라 할 일을 마쳤는지 아닌지 확인할 때 사용합니다.

 

리액트 앱 만들기

프로젝트1의 준비 과정과 동일합니다.

  1. 문서(Documents) 아래에 새로운 폴더 ‘project2’ 만듭니다.
  2. 비주얼 스튜디오 코드에서 project2 폴더를 불러온 다음, 터미널을 열고 npx create-react-app . 명령을 입력해 리액트 앱을 생성합니다.
  3. 앱을 생성했다면 다음 4개의 불필요한 파일은 삭제합니다.
    • src/App.test.js
    • src/logo.svg
    • src/reportWebVitals.js
    • setupTest.js
  4. 계속해서 App.js와 index.js에 작성된 불필요한 코드를 삭제
// App.js
import "./App.css";

function App() {
  return <div className="App"></div>;
}
export default App;


// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

UI 구현하기

페이지 레이아웃 만들기

[할 일 관리] 앱의 최종 모습

  [할 일 관리] 앱의 UI 요소는 좌우 여백이 넓으며 페이지의 정중앙에 자리 잡고 있습니다.

 

  • index.css
body {
  margin: 0px;
}

<body> 태그의 margin을 0px로 설정 → 페이지의 외부 여백 전부 사라지게 합니다.

 

  • App.css
.App {
  max-width: 500px; // 앱 페이지의 최대 너비를 500px로 고정
  width: 100%; // 페이지 너비를 브라우저의 100%로 설정
  margin: 0 auto; // 여백을 위아래는 0, 좌우는 자동으로 설정
  box-sizing: border-box; // 내부 여백이 요소의 크기에 영향을 미치지 않도록 설정
  padding: 20px; // 내부 여백을 20px로 설정
  border: 1px solid gray; // 경계선을 1px 두께의 회색 실선으로 표시, 경계선 표시를 위한 것으로 이후 삭제 예정
}
  • 브라우저 100%로 설정해도 max-width 때문에 500px 이상으로 페이지의 너비가 늘어나지 않습니다.
    ⇒ [할 일 관리] 앱 페이지는 최대 500px의 너비를 가지며 너비가 500px보다 작으면 페이지는 브라우저의 너비가 됩니다.
  • 좌우 여백을 자동으로 설정 → UI 요소를 브라우저 가운데 배치를 위해 여백이 자동으로 조절됩니다.
  • box-sizing : 요소의 크기를 어떤 것을 기준으로 계산할지 정하는 속성
    border-box로 설정시 내부 여백이 요소의 크기에 영향을 미치치 않도록 설정합니다.

 

  • App.js
    3개의 자식 컴포넌트 Header, TodoEditor, TodoList를 각각 세로로 배치
    ( 자식 컴포넌트를 아직 구현하지 않았기에 임시 요소로 대신 배치 )
import "./App.css";

function App() {
  return (
    <div className="App">
      <div>Header</div>
      <div>Todo Editor</div>
      <div>Todo List</div>
    </div>
  );
}
export default App;

 

  • App.css 
    요소 사이에 간격이 없어 답답해 보이기에 요소의 배치 간격 수정
.App {
  (...)
  display: flex; 
  flex-direction: column; // App 컴포넌트의 요소를 수직으로 배치
  gap: 30px; // 자식 요소 간의 여백을 조절하는 속성
}

display : 페이지의 요소를 브라우저에서 어떻게 보여줄지 결정하는 속성

  • block : 기본값으로 배치 요소들을 한 줄로 꽉 채우며 세로로 배치
  • flex : 요소를 수직이 아닌 수평으로 배치
  • gap과 같이 요소의 간격을 조정하는 속성을 추가로 사용 가능

왼쪽 : flex 기능 이용 전 / 오른쪽 :  flex 기능을 이용해 요소 사이의 간격 조정한 후

 

 

Header 컴포넌트 만들기

src에 이 프로젝트의 컴포넌트 파일을 한곳에 모아 둘 component 폴더 생성 후 component 폴더에 Header.js 생성합니다.

  • App.js
    Header 컴포넌트를 페이지에 렌더링하려면 App의 자식으로 배치해야 합니다.
import "./App.css";
import Header from "./component/Header";

function App() {
  return (
    <div className="App">
      <Header />
      <div>Todo Editor</div>
      <div>Todo List</div>
    </div>
  );
}
export default App;

 

  • Header.js
    오늘의 날짜를 렌더링합니다.
import "./Header.css"; // Header.css 파일을 불러와 작성한 스타일 규칙을 적용

const Header = () => {
  return (
    <div className="Header">
      <h3>오늘은 📅</h3>
      <h1>{new Date().toDateString()}</h1> 
    </div>
  );
};
export default Header;
  • 현재의 날짜와 시간을 저장하는 Date 객체 만들고, toDateString 메서드를 이용해 날짜를 문자열로 표시합니다.

 

  • Header.css
.Header h1 {
  margin-bottom: 0px;
  color: #1f93ff;
} // <h1> 태그 요소의 여백을 0으로 하고 글꼴 색을 지정

 

Header 컴포넌트 최종 화면

 

 

TodoEditor 컴포넌트 만들기

TodoEditor 컴포넌트의 모습

component 폴더에 컴포넌트와 스타일을 정의할 TodoEditor.js와 TodoEditor.css 를 각각 생성합니다.

 

  • TodoEditor.js
    요소의 제목, 할 일 아이템을 생성하는 입력 폼, 클릭하면 실제 할 일 아이템을 생성하는 버튼으로 구성되어 있습니다.
import "./TodoEditor.css";

const TodoEditor = () => {
  return (
    <div className="TodoEditor">
      <h4>새로운 Todo 작성하기 ✏️ </h4>
      <div className="editor_wrapper">
        <input placeholder="새로운 Todo..." />
        <button>추가</button>
      </div>
    </div>
  );
};
export default TodoEditor;

 

  • TodoEditor.css
.TodoEditor .editor_wrapper {
  width: 100%;
  display: flex;
  gap: 10px;
} // 입력 폼과 버튼을 감싸는 요소에 스타일을 설정

.TodoEditor input {
  flex: 1;
  box-sizing: border-box;
  border: 1px solid rgb(220, 220, 220);
  border-radius: 5px;
  padding: 15px;
} // 입력 폼에 스타일을 설정

.TodoEditor input:focus { 
  outline: none;
  border: 1px solid #1f93ff;
} // 입력 폼을 클릭 했을 때의 스타일을 설정

.TodoEditor button { 
  cursor: pointer;
  width: 80px;
  border: none;
  background-color: #1f93ff;
  color: white;
  border-radius: 5px;
} // 입력 폼 오른쪽에 위치할 버튼 스타일을 설정
  • flex를 1로 설정 → 해당 요소의 너비가 브라우저의 크기에 따라 유연하게 변경됩니다.
  • outline 속성을 none으로 설정 → 입력 폼을 클릭했을 때 두꺼운 경계선 생기지 않습니다.
  • cursor 속성을 pointer로 설정 → 버튼에 마우스 포인터를 올릴 때 손 모양으로 변경됩니다.

TodoEditor 컴포넌트 최종 화면

 

 

TodoList, TodoItem 컴포넌트 만들기

TodoList 컴포넌트 만들기 

TodoList 컴포넌트의 모습

  • TodoList.js
import "./TodoList.css";

const TodoList = () => {
  return (
    <div className="TodoList">
      <h4>Todo List 🌱</h4>
      <input className="searchbar" placeholder="검색어를 입력하세요" />
    </div>
  );
};
export default TodoList;

 

  • TodoList.css
/* 검색 폼에 스타일 적용 */
.TodoList .searchbar {
  margin-bottom: 20px;
  width: 100%;
  border: none;
  border-bottom: 1px solid rgb(220, 220, 220);
  box-sizing: border-box;
  padding-top: 15px;
  padding-bottom: 15px;
}

/* 검색 폼을 클릭했을 때의 스타일 적용 */
.TodoList .searchbar:focus {
  outline: none;
  border-bottom: 1px solid #1f93ff;
}

 

TodoList 컴포넌트의 검색 폼 최종 화면 ( 아직 TodoItem 컴포넌트 만들지 않았으므로 할 일 아이템을 출력하지 못합니다. )

 

 

TodoItem 컴포넌트 만들기

TodoItem 컴포넌트의 모습

 

  • TodoItem.js
import "./TodoItem.css";

const TodoItem = () => {
  return (
    <div className="TodoItem">
      <div className="checkbox_col">
        <input type="checkbox" />
      </div>
      <div className="title_col">할 일</div> 
      <div className="date_col">{new Date().toLocaleDateString()}</div> 
      <div className="btn_col"> 
        <button>삭제</button>
      </div>
    </div>
  );
};
export default TodoItem;
  • checkbox_col - 할 일 아이템 가장 왼쪽에는 할 일 완료 여부를 표시하는 체크박스를 배치
  • title_col - 사용자가 작성한 할 일을 렌더링할 요소를 배치 → 임시로 ‘할 일’이라는 문자열을 렌더링
  • date_col - 할 일 아이템이 작성된 시간을 렌더링할 요소를 배치 → 임시로 현재 시각을 렌더링

 

 

TodoList에 TodoItem 컴포넌트 배치하기

  • TodoList.js
import TodoItem from "./TodoItem";
import "./TodoList.css";

const TodoList = () => {
  return (
    <div className="TodoList">
      <h4>Todo List 🌱</h4>
      <input className="searchbar" placeholder="검색어를 입력하세요" />
      <div className="list_wrapper">
        <TodoItem />
        <TodoItem />
        <TodoItem />
      </div>
    </div>
  );
};
export default TodoList;
  • list_wrapper - 여러 개의 할 일 아이템을 리스트로 보여줄 <div> 태그 요소를 배치
    → 현재 리스트로 보여 줄 할 일 아이템이 없으므로 임시로 TodoItem을 3개 배치

 

  • TodoList.css
    리스트로 배치한 3개의 아이템의 간격 수정 → TodoItem을 감싸고 있는 list_wrapper 요소에 스타일링 적용되게 설정합니다.
TodoList .list_wrapper {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

 

 

  • TodoItem.css
/* 할 일 아이템 박스 스타일 적용 */
.TodoItem {
  display: flex;
  align-items: center;
  gap: 20px;
  padding-bottom: 20px;
  border-bottom: 1px solid rgb(240, 240, 240);
}

/* 체크박스를 감싼 박스에 스타일 적용 */
.TodoItem .checkbox_col {
  width: 20px;
}

/* 할 일 텍스트를 감싼 박스에 스타일 적용 */
.TodoItem .title_col {
  flex: 1;
}

/* 할 일 아이템 등록 시간을 감싼 박스에 스타일 적용 */
.TodoItem .date_col {
  font-size: 14px;
  color: gray;
}

/* 삭제 버튼에 스타일 적용 */
.TodoItem .btn_col button {
  cursor: pointer;
  color: gray;
  font-size: 14px;
  border: none;
  border-radius: 5px;
  padding: 5px;
}

왼쪽 : 스타일 지정 전 / 오른쪽 : TodoItem 컴포넌트의 스타일 지정 후

 

 

⇒ [할 일 관리] 앱의 UI 구현을 모두 완료하였으므로 App.css의 border 속성 제거

[할 일 관리] 앱의 최종 UI

 

+ UI 구현을 흔히 ‘퍼블리싱’ 또는 ‘UI 개발’이라고 합니다. UI 개발은 데이터를 가공하고 상태를 관리하는 구현 기능과 더불어 프론트엔드 엔지니어의 기본 소양 중 하나이므로 꾸준히 학습하고 경험을 쌓는 게 꼭 필요합니다.


기능 구현 준비하기

각 컴포넌트별 기능

  • App 컴포넌트: 할 일 데이터 관리하기
  • Header 컴포넌트: 오늘의 날짜 표시
  • TodoEditor 컴포넌트: 새로운 할 일 아이템 생성
  • TodoList 컴포넌트: 검색에 따라 필터링된 할 일 아이템 렌더링
  • TodoItem 컴포넌트: 할 일 아이템의 수정 및 삭제

컴포넌트는 주로 데이터를 다루는 기능으로 이루어져 있습니다.

  • Create: 할 일 아이템 생성
  • Read: 할 일 아이템 렌더링
  • Update: 할 일 아이템 수정
  • Delete: 할 일 아이템 삭제

→ 데이터를 다루는 4개의 기능, 즉 추가(Create), 조회(Read), 수정(Update), 삭제(Delete) 기능을 앞글자만 따서 CRUD라고 합니다.

 

기초 데이터 설정하기

  • App.js
import { useState } from "react";
(..)

function App() {
  const [todo, setTodo] = useState([]); 

  return (
    (...)
  );
}
export default App;
  • useState를 이용해 할 일 아이템의 상태를 관리할 State 생성
  • 함수 useState에서 인수로 빈 배열을 전달 → todo의 기본값을 빈 배열로 초기화
    State 변수 todo : 데이터를 저장하는 배열 + 일종의 데이터베이스 역할

 

데이터 모델링하기

데이터 모델링 : 현실의 사물이나 개념을 프로그래밍 언어의 객체와 같은 자료구조로 표현 하는 행위

[할 일 관리] 앱의 할 일 아이템 데이터 모델링

 

하나의 할 일 아이템에 담겨있는 정보

  • 일의 완료 여부 - isDone
  • 일의 종류 - content
  • 생성 날짜 - createdDate
  • id( = 고유 식별자 ) : 페이지에 렌더링하지는 않지만 아이템을 구별하기 위해 필요한 정보
    ⇒ 고유 식별자가 없으면 특정 아이템 삭제나 수정 등의 연산이 불가능하기에 아이템을 구별하기 위해 필요합니다.
{
  id: 0, 
  isDone: false, 
  content: "React 공부하기", 
  createdDate: new Date().getTime(), 
}
  • id : 간단하게는 0부터 시작해 아이템을 추가할 때마다 1씩 늘어나도록 id에 값을 부여할 수 있습니다.
  • isDone : 불리언 자료형으로 현재 상황에서 할 일이 완료 되었는지 여부를 확인할 때 이용합니다.
  • createDate : new Date()로 Date 객체를 만들고 getTime 메서드를 이용해 이 객체를 타임 스탬프값으로 변환합니다.
    → 타임 스탬프값으로 시간을 저장하면 보관할 데이터의 양이 대폭 줄어듭니다.

데이터를 모델링 하는 이유 : 데이터를 어떻게 관리할지 생각하기 위함입니다.

  • 모델링 과정이 잘못되면 작업 과정에서 큰 문제가 발생할 수 있습니다.
    → 문제가 발생하면 데이터를 관리하는 모든 과정을 수정하게 되며 아예 프로젝트를 처음부터 다시 시작하는 상황이 발생합니다.
  • 많은 시간을 들여서라도 데이터 모델링의 완성도를 높이는 게 프로젝트의 시간이나 비용을 줄이는 데 큰 도움이 됩니다.

 

목 데이터 설정하기

목(Mock) 데이터란 모조품 데이터라는 뜻으로 기능을 완벽히 구현하지 않은 상태에서 테스트를 목적으로 사용하는 데이터입니다.
→ 기능이 개발되지 않아 데이터가 없는 상황일 때 임시 데이터 역할을 하는 목 데이터를 사용하면 데이터 관리 기능 개발이 한결 수월해집니다.

 

  • App.js
(...)
const mockTodo = [ // 3개의 객체를 저장하는 배열 목 데이터 생성
  {
    id: 0,
    isDone: false,
    content: "React 공부하기",
    createdDate: new Date().getTime(),
  },
  {
    id: 1,
    isDone: false,
    content: "빨래 널기",
    createdDate: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    content: "노래 연습하기",
    createdDate: new Date().getTime(),
  },
];

function App() {
  const [todo, setTodo] = useState(mockTodo); 
	// todo의 기본값으로 목 데이터를 전달

  return (
    (...)
  );
}
export default App;

 

+ “(변수명) is assigned a value but never used” 라는 경고 메시지

선언된 변수를 어디에서도 사용하지 않았을 때 발생하는 경고로 오류가 아닙니다. 따라 앞으로의 과정에선 무시해도 됩니다.

 

→ 아직 TodoList 컴포넌트에 목 데이터를 전달하지 않았기 때문에 데이터를 페이지에 렌더링하지는 않습니다.

데이터가 잘 설정되는지 확인하기 위해 개발자 도구에서 [Components] 탭울 통해 App 컴포넌트의 hooks 항목에서 State의 값을 확인해야 합니다.

[Components] 탭에서 목 데이터 확인하기

  • 3개의 목 데이터가 나온다면 정상적으로 동작하는 겁니다.
  • 빈 배열만 표시된다면 페이지 새로고침하여 App 컴포넌트를 다시 마운트한 후 확인합니다.

Create: 할 일 추가하기

기능 흐름 살펴보기

할 일이 추가되는 과정을 도식화

[할 일 관리] 앱의 Create 기능 흐름

  1. 사용자가 새로운 할 일을 입력합니다.
  2. TodoEditor 컴포넌트에 있는 <추가> 버튼을 클릭합니다.
  3. TodoEditor 컴포넌트는 부모인 App에게 아이템 추가 이벤트가 발생했음을 알리고 사용자가 추가한 할 일 데이터를 전달합니다.
  4. App 컴포넌트는 TodoEditor 컴포넌트에서 받은 데이터를 이용해 새 아이템을 추가한 배열을 만들고 State 변수 todo 값을 업데이트합니다.
  5. TodoEditor 컴포넌트는 자연스러운 사용자 경험을 위해 할 일 입력 폼을 초기화합니다.

 

아이템 추가 함수 만들기

TodoEditor 컴포넌트에서 <추가> 버튼 클릭시 App에 사용자가 입력한 할 일 데이터를 전달하고 추가 이벤트가 발생했음을 알려야 합니다.

  • App 컴포넌트
    새 할 일 아이템을 추가하는 함수 onCreate 생성
(...)
function App() {
  const idRef = useRef(3); // 앞에 작성한 목 데이터 id가 0, 1, 2이라 3부터 시작

  const onCreate = (content) => {
    const newItem = {
      id: idRef.current, 
      content,
      isDone: false,
      createdDate: new Date().getTime(),
    };
    setTodo([newItem, ...todo]);
    idRef.current += 1; 
  };

  return (
    (...)
  );
}
export default App;
  • idRef를 활용해 id값이 1씩 늘어나도록 설정해 아이템이 고유한 id 가지게 합니다.
    id : 0 으로 설정하면 0으로 고정되어 고유한 id값을 가질 수 없기에 Ref 객체 사용

 

  • App.js
(...)
function App() {
  (...)
  return (
    <div className="App">
      <Header />
      <TodoEditor onCreate={onCreate} />
      <TodoList />
    </div>
  );
}
export default App;

 

 

아이템 추가 함수 호출하기

  • TodoEditor 컴포넌트
    함수 onCreate 사용하기 위해 수정
import { useState } from "react";
import "./TodoEditor.css";

const TodoEditor = ({ onCreate }) => {
  const [content, setContent] = useState(""); 
  const onChangeContent = (e) => {
    setContent(e.target.value);
  };
  const onSubmit = () => { 
    onCreate(content);
   }
  return (
    <div className="TodoEditor">
      <h4>새로운 Todo 작성하기 ✏ </h4>
      <div className="editor_wrapper">
        <input 
          value={content}
          onChange={onChangeContent}
          placeholder="새로운 Todo..."
        />
		<button onClick={onSubmit}>추가</button> 
     </div>
    </div>
  );
};
export default TodoEditor;

 

  • State 변수 content : 사용자가 입력 폼에 입력한 데이터 저장
  • value 속성으로 content 값 설정, 이벤트 핸들러로 onChangeContent 설정
  • onSubmit 함수 : onCreate를 호출하고 인수로 content의 값 전달
    아직 Read 기능은 개발되지 않아 개발도구 [Components] 탭에서 확인해야 합니다.

+ 버튼을 클릭하는 이벤트가 발생했을 때 인수로 전달하는 데이터는 컴포넌트 트리 구조상의 데이터 전달이 아니라 일종의 ‘이벤트’가 전달된다고 생각하면 됩니다.

→ 컴포넌트 트리 구조에서 데이터를 전달한다는 의미는 여러 컴포넌트가 동시에 동일한 데이터를 이용한다는 뜻이라 변하는 값인 State는 부모에서 자식으로 Props를 이용해서만 전달할 수 있습니다.

 

Create 완성도 높이기

빈 입력 방지하기

빈 입력란에 포커스를 주는 기능 구현
→ 입력 폼을 관리할 Ref 객체 만들어 함수 onSubmit에서 content 값이 비어 있으면 입력 폼에 포커스를 구현하는 방식

 

  • TodoEditor 컴포넌트 수정
import { useState, useRef } from "react";
import "./TodoEditor.css";

const TodoEditor = ({ onCreate }) => {
  const [content, setContent] = useState("");
  const inputRef = useRef(); 

  (...)
  const onSubmit = () => {
    if (!content) { 
      inputRef.current.focus();
      return;
    } 
    onCreate(content);
  };

  return (
    <div className="TodoEditor">
      <h4>새로운 Todo 작성하기 ✏ </h4>
      <div className="editor_wrapper">
        <input
          ref={inputRef} 
          value={content}
          onChange={onChangeContent}
          placeholder="새로운 Todo..."
        />
        <button onClick={onSubmit}>추가</button>
      </div>
    </div>
  );
};
export default TodoEditor;
  • 함수 onSubmit - 현재 content 값이 빈 문자열이면, inputRef가 현잿값(current)으로 저장한 요소에 포커스하고 종료
  • 할 일 입력 폼(input)의 ref에 inputRef 설정 ⇒ inputRef가 현재값으로 이 요소를 저장합니다.

 

아이템 추가 후 입력 폼 초기화하기

아이템 추가하면 자동으로 할 일 입력 폼 초기화하는 기능 구현

 

  • TodoEditor 컴포넌트의 함수 onSubmit 수정
(...)
const TodoEditor = ({ onCreate }) => {
  (...)
  const onSubmit = () => {
    if (!content) {
      inputRef.current.focus();
      return;
    }
    onCreate(content);
    setContent("");
  };
  (...)
};
export default TodoEditor;

함수 setContent를 호출해 인수로 빈 문자열을 전달 → 새 아이템 추가 후 content 값은 빈 문자열이 되며 입력 폼 초기화

 

 

<Enter>키를 눌러 아이템 추가하기

키보드의 <Enter> 키를 누르면, <추가> 버튼을 클릭한 것과 동일한 동작을 수행하는 키 입력 이벤트 구현

 

  • TodoEditor 컴포넌트 수정
(...)
const TodoEditor = ({ onCreate }) => {
  (...)
  const onKeyDown = (e) => { 
    if (e.keyCode === 13) {
      onSubmit();
    } // 13은 <Enter> 키를 의미함
  };
  return(
    <div className="TodoEditor">
      <h4>새로운 Todo 작성하기 ✏ </h4>
      <div className="editor_wrapper">
        <input
          ref={inputRef}
          value={content}
          onChange={onChangeContent}
          onKeyDown={onKeyDown} 
          placeholder="새로운 Todo..."
        />
        <button onClick={onSubmit}>추가</button>
      </div>
    </div>
  )
};
export default TodoEditor;
  • 함수 onKeyDown : 사용자가 <Enter> 키를 눌렀을 때 호출할 이벤트 핸들러

Quiz

  1. 현재 날짜와 시간을 저장하는 Date 객체를 문자열로 표시하기 위해선 ( toDateString ) 메서드를 이용해야 합니다.
  2. ( flex )를 1로 설정하면 해당 요소의 너비가 브라우저의 크기에 따라 유연하게 변경됩니다.
  3. ( cursor )의 속성값을 변경하면 마우스 포인터 모양을 원하는 형태로 변경할 수 있습니다.
  4. ( CRUD ) 란 데이터를 다루는 4개의 기능, 즉 추가, 조회, 수정, 삭제 기능을 의미합니다.
  5. ( 목 데이터 ) 란  모조품 데이터라는 뜻으로 기능을 완벽히 구현하지 않은 상태에서 테스트를 목적으로 사용하는 데이터입니다.
  6. 모든 아이템이 고유한 id를 가지게 하려면 ( Ref 객체 )를 사용해야 합니다.
  7. 입력 폼을 초기화 하기 위해선 함수 setContent를 호출해 인수로 ( 빈 문자열 )을 전달해야 합니다. 

 

코드 작성

1. 새 할 일 아이템을 추가하는 함수 onCreate를 생성하기 → Ref 객체를 사용해 아이템마다 고유한 id를 가지게 하며 목 데이터를 사용하지 않았음을 주의하기

import { useState } from "react";
(..)

function App() {
  const [todo, setTodo] = useState([]); 

  return (
    (...)
  );
}
export default App;

 

정답 :

더보기
import { useState, useRef } from "react";
(...)

function App() {
  const [todo, setTodo] = useState([]);
  const idRef = useRef(0); 

  const onCreate = (content) => {
    const newItem = {
      id: idRef.current, 
      content,
      isDone: false,
      createdDate: new Date().getTime(),
    };
    setTodo([newItem, ...todo]);
    idRef.current += 1; 
  };

  
  return (
    (...)
  );
}
export default App;

 

2. 입력 폼이 비어 있으면 입력 폼에 포커스하며 아이템이 추가되면 자동으로 입력 폼이 초기화되도록 아래 코드 수정하기

import { useState } from "react";
import "./TodoEditor.css";

const TodoEditor = ({ onCreate }) => {
  const [content, setContent] = useState(""); 
  const onChangeContent = (e) => {
    setContent(e.target.value);
  };
  const onSubmit = () => { 
    onCreate(content);
   }
  return (
    <div className="TodoEditor">
      <h4>새로운 Todo 작성하기 ✏ </h4>
      <div className="editor_wrapper">
        <input 
          value={content}
          onChange={onChangeContent}
          placeholder="새로운 Todo..."
        />
       <button onClick={onSubmit}>추가</button> 
     </div>
    </div>
  );
};
export default TodoEditor;

 

정답 :

더보기
import { useState, useRef } from "react";
import "./TodoEditor.css";

const TodoEditor = ({ onCreate }) => {
  const [content, setContent] = useState("");
  const inputRef = useRef(); 

  (...)

  const onSubmit = () => {
    if (!content) { 
      inputRef.current.focus();
      return;
    } 
    onCreate(content);
    setContent("");
  };

  return (
    <div className="TodoEditor">
      <h4>새로운 Todo 작성하기 ✏ </h4>
      <div className="editor_wrapper">
        <input
          ref={inputRef} 
          value={content}
          onChange={onChangeContent}
          placeholder="새로운 Todo..."
        />
        <button onClick={onSubmit}>추가</button>
      </div>
    </div>
  );
};
export default TodoEditor;

출처 :  이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023)

Corner React.js 1

Editor: ssxbin

728x90

관련글 더보기