상세 컨텐츠

본문 제목

[리액트 스타터1] 10장 : 일정 관리 웹 애플리케이션 만들기

22-23/22-23 리액트 스타터 1

by 2jo 2022. 12. 22. 10:00

본문

728x90

 

 

 

이번 10장에서는 컴포넌트의 일정관리 웹 애플리케이션에 대해서 알아보고자 합니다.

지금까지 배운 이론을 바탕으로 실제로 사용 가능한 웹 용 애플리케이션을 만들어 봅시다.


 

 

 

 

 

 

 

10.1 프로젝트 준비하기

 

 

이전의 이론을 바탕으로 애플리케이션을 생성하기에 앞서, 라이브러리 설치 및 기타 프로그램 설정과 생성이 필요합니다. 이를 단계별로 나누어 알아봅시다.

 

1) 라이브러리 몇 가지를 다운로드합니다.

명령프롬포트 창 등을 통해 라이브러리를 설치하면 되는데, 설치할 라이브러리는 우선 3가지입니다.

 

  • SASS
  • CLASSNAMES (편리한 조건부 스타일링 제공)
  • REACT-ICONS (다양한 SVG 아이콘 제공)

 

 

2) Prettier 설정을 합니다.

2장에서 Prettier에 대해 학습했었습니다. 이를 참고해서 프로젝트를 만들 수 있습니다.

지금까지 제작한 프로젝트 최상위 디렉터리에 [.prettierrc] 파일 생성합니다.

생성 내용은 아래의 코드와 같습니다.

// [.prettierrc] 파일 내용

{
  “singleQuote“: true,
  “semi“: true,
  “useTabs“: false,
  “tabWidth“: 2,
  “trailingComma“: “all“,
  “printWidth“: 80
}
 

 

 

3) 프로젝트 글로벌 스타일 파일(index.css)을 수정합니다.

기존 설정을 지우고 배경을 회색으로 만드는 아스키코드(#e9ecef)를 넣어 아래와 같이 수정합니다.

 

// [index.css] 파일 내용

body {
  margin: 0;
  padding: 0;
  background: #e9ecef;
}
 

 

4) 기존의 App 컴포넌트를 초기화해 새로 정리합니다.

아래의 코드와 같이 컴포넌트의 기본 틀을 잡는 파일로 수정해 줍니다.

 

// [App.js] 파일 내용

import React from 'react';
 
const App = () => {
  return <div>Todo App 생성 프로젝트!</div>;
};
 
export default App;
 

 

 

5) 프로젝트 디렉터리에 "yarn start"를 눌러 개발 서버를 구동해 줍니다. 

 

 

이렇게 기본적인 설정 및 개발 준비가 끝났습니다.

다음으로, UI를 만들어 봅시다.

 

 

 

 

 

 

10.2 UI 구성하기

 

우리가 만들 컴포넌트는 각각의 기능별로 구분했을 때 총 4가지입니다. 관습상 [ src > components ] 디렉터리를 생성해 이를 저장합니다.

 

  • TodoTemplate (화면 가운데 정렬, 앱 타이틀 제공, 내부 JSX -> children -> props)
  • TodoInsert (새로운 항목 입력 및 추가, 새로운 입력 상태 관리)
  • TodoListItem (각 항목 정보 제공, todo 객체 -> props, 각기 다른 스타일의 UI 제공)
  • TodoList (todo배열 -> props -> map -> TodoListItern 컴포넌트)

 

여기 네 개의  템플릿을 생성한 후, App.js에 이를 불러와 렌더링 하면 UI 구성이 끝납니다.

다음으로, 각각의 템플릿에 작성할 코드에 대해 알아봅시다.

 

 

1) 먼저 TodoList 코드입니다.

js파일과 scss 파일 두 가지를 생성합니다.

 

[TodoTemplate.js] 파일은 아래의 코드와 같습니다.

// [TodoTemplate.js] 파일 생성

import React from ‘react‘;
import ‘./TodoTemplate.scss‘;


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

 

[TodoTemplate.scss] 파일은 아래와 같습니다.

// [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;
  }
}
 
flex에 대한 자세한 설명 참고 사이트 : 

 

 

다음으로, 앞서 생성한 [App.js] 파일을 불러와 만든 [TodoTemplate.js]를 랜더링 하는 코드를 추가합니다. 이는 모든 4개의  템플릿을 생성할 때마다 추가해야 하므로, 맨 마지막에 코드를 정리하겠습니다.

 

 

자동 import 문 작성하기

* 추가하려는 컴포넌트가 탭으로 열려있는 경우 : 자동 생성
* 추가하려는 컴포넌트가 탬으로 열려있지 않는 경우 : 프로젝트 최상위 디렉터리에 [ jsconfig.json ] 파일 생성. (ctrl+space, enter 키 입력으로 자동 생성 가능)

 

// [jsconfig.json] 파일. 

{
  "compilerOptions": {
    "target": "es6"
  }
} 
 

 

2) 다음으로, TodoInsert 코드를 생성합니다.

마찬가지로, js와 scss 파일을 생성합니다.

TodoInsert.js 코드는 아래와 같습니다.

// [TodoInsert.js] 파일 생성

import React from 'react';
import { MdAdd } from 'react-icons/md'; // 
import './TodoInsert.scss';
 
const TodoInsert = () => {
  return (
    <form className="TodoInsert">
      <input placeholder="할 일을 입력하십시오" />
      <button type="submit">
        <MdAdd />
      </button>
    </form>
  );
};
 
export default TodoInsert;
 

 

react-icons 에 대한 자세한 설명 참고 사이트 : 

 

TodoInsert.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;
    }
  }
}
 

App.js에서 TodoTemplate를 불러온 것과 같이, [TodoInsert.js]를 불러와 렌더링 합니다.

 

 

3) 세 번째로, js와 scss파일을 TodoListItem에 대한 내용으로 생성합니다.

TodoListItem.js의 소스 코드는 아래와 같습니다.

// [TodoListItem.js] 파일 생성

import React from ‘react‘;
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from ‘react-icons/md‘;
import ‘./TodoListItem.scss‘;


const TodoListItem = () => {
  return (
    <div className=“TodoListItem“>
      <div className=“checkbox“>
        <MdCheckBoxOutlineBlank />
        <div className=“text“>할 일</div>
      </div>
      <div className=“remove“>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};
export default TodoListItem;
 

scss의 소스 코드는 아래입니다.

// [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;
  }
}
 

 

4) 네 번째로, TodoList의 js와 scss 파일을 생성합니다.

먼저 js파일의 코드는 아래와 같습니다.

// [TodoList.js] 파일 생성

import React from ‘react‘;
import TodoListItem from ‘./TodoListItem‘;
import ‘./TodoList.scss‘;


const TodoList = () => {
  return (
    <div className=“TodoList“>
      <TodoListItem />
      <TodoListItem />
      <TodoListItem />
    </div>
  );
};
export default TodoList;
 

다음으로 scss 코드는 아래와 같습니다.

// [TodoList.scss] 파일 생성

.TodoList {
  min-height: 320px;
  max-height: 513px;
  overflow-y: auto;
}
 

 

지금까지 생성한 4개의 js  템플릿을 App.js에 불러와 렌더링 합니다.

 

// [App.js] 생성 파일

import React from 'react';
import TodoTemplate from './components/TodoTemplate';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
 
const App = () => {
  return (
    <TodoTemplate>
      <TodoInsert />
      <TodoList />
    </TodoTemplate>
  );
};
export default App;
 

 

 

 

 

10.3 기능 구현하기

 

생성한 UI를 토대로, 여러 가지 기능을 구현해보고자 합니다.

이는 App.js 파일에 소스 코드를 추가힙으로서 설정할 수 있습니다.

다양한 기능을 구현하는 방법을 순차적으로 살펴봅시다.

 

 

 

1) todos 상태 사용

아래는 일정 항목에 대한 상태를 조정하는 소스코드로, 'use state'를 사용해 'todos' 상태를 정의하고, 이를 'TodoList'의 props로 전달하는 내용입니다.

 

// [App.js] 파일 수정

import React, { useState } 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,
    },
  ]);



return (
    <TodoTemplate>
      <TodoInsert />
      <TodoList todos={todos} />                         // 새로 수정한 내용
    </TodoTemplate>
  );
};
export default App;
 

또한 [TodoList.js]의 수정이 필요합니다.

TodoList의 props에서 각 항목의 고유 id, 내용, 완료여부 등의 값을 전달받아, 다시 todoItem으로 변환해 랜더링 하도록 설정해야 하기 때문입니다.

 

수정한 코드는 아래와 같습니다.

// [TodoList.js] 파일 수정

import React from 'react';
import TodoListItem from './TodoListItem';
import './TodoList.scss';
 
const TodoList = ({ todos }) => {                        // 새로 수정한 내용
  return (
    <div className="TodoList">
      {todos.map(todo => (                               // 새로 수정한 내용
        <TodoListItem todo={todo} key={todo.id} />       // 새로 수정한 내용
      ))}                                                // 새로 수정한 내용
    </div>
  );
};
 
export default TodoList;
 

 

새로 받아 온 'todo' 값에 따라 수정된 UI를 노출시키기 위해서, [TodoListItem.js] 파일을 아래의 코드와 같이 수정합니다.

// [TodoListItem.js] 파일 수정

import React from ‘react‘;
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from ‘react-icons/md‘;
import cn from ‘classnames‘;                                            // 새로 수정한 내용
import ‘./TodoListItem.scss‘;


const TodoListItem = ({ todo }) => {                                   // 새로 수정한 내용
  const { text, checked } = todo;                                      // 새로 수정한 내용



return (
    <div className=“TodoListItem“>
      <div className={cn(‘checkbox‘, { checked })}>                   // 새로 수정한 내용
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}       // 새로 수정한 내용
        <div className=“text“>{text}</div>                            // 새로 수정한 내용
      </div>
      <div className=“remove“>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};

export default TodoListItem;
 

 

2) 항목 추가 기능 구현

일정 항목을 추가하는 기능을 구현하기 위해서는 [TodoInsert.js]와 [App.js]에서의 수정이 필요합니다.

먼저 'useState'를 사용해서 value 상태를 정의해 TodoInsert 컴포넌트에서 인풋의 입력 값을 관리할 수 있도록 만듭니다. 그리고 'onChange'함수를 생성해 인풋에 넣어주고, 자동으로 재사용할 수 있도록 'useCallback'을 이용합니다.

// [TodoInsert.js] 파일 수정

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

const TodoInsert = () => {                         
  const [value, setValue] = useState(“);                         // 새로 수정한 부분

const onChange = useCallback(e => {                              // 새로 수정한 부분
// conslog.log를 통해 입력 값이 잘 전달되는지 확인 가능
    setValue(e.target.value);                                    // 새로 수정한 부분
  }, []);                                                        // 새로 수정한 부분


return (
    <form className=“TodoInsert“>
      <input
        placeholder=“할 일을 입력하세요“
        value={value}                                            // 새로 수정한 부분
        onChange={onChange}                                      // 새로 수정한 부분
      />
      <button type=“submit“>
        <MdAdd />
      </button>
    </form>
  );
};
export default TodoInsert;

 

인풋에 입력한 텍스트가 잘 전달되는지 확인하는 방법 :

* value와 onChange 함수 설정 - coonsol.log 를 통해 확인하기
* 리액트 개발자 도구 사용하기 (https://chrome.google.com/webstore/category/extensions)
 

 

다음으로, [App.js] 파일을 수정해 todos 배열에 새 객체를 추가합니다.

바로 'onInsert'함수를 이용하는 것인데요, 새로운 객체를 만들 때마다 id 값에 1씩 더해준다는 특징이 있습니다.

또한 이 함수를 TodoInsert의 컴포넌트인 props로 설정합니다.

 

// [App.js] 파일 수정

import React, { 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; // nextId 1씩 더하기
    },
    [todos],                                             //새로 수정 
  );



return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />                    //새로 수정 
      <TodoList todos={todos} />
    </TodoTemplate>
  );
};
export default App;
 

 

그리고 [TodoInsert.js] 파일을 한번 더 수정해줄 텐데, onSubmit 이벤트를 설정해 버튼을 클릭하면 onInsert 함수에 현재 value 값을 파라미터로 넣어서 호출하며 현재 value 값을 초기화하는 기능이 있습니다.

그뿐만 아니라, 브라우저를 새로고침 합니다.

 

수정한 코드는 아래와 같습니다.

 

// [TodoInsert.js] 파일 수정

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

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

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

const onSubmit = useCallback(
    e => {
      onInsert(value);
      setValue(“); // value 값 초기화

      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;
 
onSubmit 이벤트와 onClick 이벤트의 차이점 :

* onSubmit 이벤트 : 인풋에서 Enter 키를 눌러도 이벤트 발생
* onClick 이벤트 : 인풋에서 Enter 키를 누르면 이벤트 발생하지 않음

 

3) 지우기 기능

배열 내장함수인 filter를 사용하는 리액트 컴포넌트에서 배열의 불변성을 지킬 수 있고, 동시에 원소를 제거할 수 있습니다. 여기에는 조건을 확인해주는 함수를 파라미터로 넣어주는데, true 값을 반환하면 새로운 배열에 포함시킬 수 있습니다.

[App.js] 파일에서 이를 사용해 같은 항목의 id를 가질 경우, todos 배열에서 삭제하는 함수를 생성할 수 있습니다.

마지막에 이를 TodoList의 props로 설정하는 것도 잊지 마세요!

 

새로 추가 및 수정한 부분은 아래의 코드와 같습니다.

// [App.js] 파일 수정 및 추가사항

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

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

 

또한 [TodoList.js] 파일에도 수정할 점이 있는데요, 바로 방금 만든 'onRemove' 함수를 'TodoListItem'에 전달한다는 부분입니다.

 

해당 코드는 아래와 같습니다.

 

// [TodoList.js] 수정 및 추가 사항

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

위의 코드에 의해서 'TodoListItem'는 전달받은 내용을 현재 자신의 id 값을 넣어 적용하는 코드는 아래와 같습니다.

 

// [TodoListItem.js] 수정 코드

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import cn from 'classnames';
import './TodoListItem.scss';
 
const TodoListItem = ({ todo, onRemove }) => {
  const { id, text, checked } = todo;
 
  return (
    <div className="TodoListItem">
      <div className={cn('checkbox', { checked })}>
        {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </div>
  );
};
export default TodoListItem;
 

 

4) 수정 기능

조금 전에 만든 삭제 기능과 유사하되, onRemove 함수 대신 onToggle이라는 함수를 생성 및 전달하면 됩니다.

먼저 [App.js]에 onToggle을 map을 이용해 구현합니다.

//  [App.js] 파일 수정

 const onToggle = useCallback(
    id => {
      setTodos(
        todos.map(todo =>      // 특정 id를 가진 객체의 checked 값을 반전
          todo.id === id ? { ...todo, checked: !todo.checked } : todo,
        ),
      );
    },
    [todos],
  );


  return (
    <TodoTemplate>
      <TodoInsert onInsert={onInsert} />
      <TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />  // 삼항 연산자. id가 다른 경우만 변화 줌.
    </TodoTemplate>
  );
};
 

그리고 TodoListItem를 수정하면 아래와 같습니다.

// [TodoListItem.js] 파일 수정


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>
  );
};
 

이어서 TodoList를 수정해 봅시다.

// [TodoList.js] 파일 수정

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

 

 

10.4 정리

 

이상으로 간단한 프로젝트 생성을 끝마쳤습니다.

다만, 이것보다 커다란 프로젝트의 경우 이 외에도, 컴포넌트 리렌더링 최적화 과정 등이 필요할 수 있습니다.

이에 대한 자세한 내용은 11장에서 다룰 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 


Corner React1

Editor: 라마

728x90

관련글 더보기