[SeSAC Front-end] React로 간단한 할일 앱 만들기😎

Harimad·2022년 11월 6일
1

React

목록 보기
1/18
post-thumbnail

0. Intro

  • 지금부터 리액트 기초를 익히고 나서 많은 사람들이 만들어보는 간단한 todo 앱을 만들어 보도록 하겠습니다.
  • todo 앱을 만드는데 부모 컴포넌트에서 자식 컴포넌트로 데이터를 넘겨서 html 태그들을 그려주는 작업을 해보겠습니다.
  • 이번에는 최적화를 생각하지 않고 오로지 todo 앱이 잘 동작하는지에 초점을 맞추어서 진행해 보겠습니다.
  • 이번 장을 마치면, 우리는 리액트의 기초를 이해하면서 간단한 todo앱을 완성할 수 있습니다.

1. 프로젝트 세팅

  • 먼저 NodeJs가 설치되어 있어야합니다.
  • Node가 설치되어 있어야 우리가 리액트 앱을 터미널에서 실행할 수 있기 때문입니다.
  • Node를 설치하면 npm과 npx가 동시에 설치 됩니다.
  • 아래의 명령어는 npx 를 이용해서 create-react-app 모듈을 실행하고 todoApp이라는 리액트 프로젝트 폴더를 생성하겠다는 명령어 입니다.
npx create-react-app todoApp
cd todoApp
  • 위의 과정을 마치면 아래와 같이 많은 폴더와 파일이 생성됩니다.

  • src 폴더안에서 App.js, App.css, index.js 파일을 제외하고, 나머지 파일은 사용하지 않기 때문에 삭제해줍니다.

  • App.css 파일안의 내용을 전부 삭제합니다. (초기화)


2. 컴포넌트 구성

  • 컴포넌트 구성은 아래와 같이 하겠습니다.
  • TodoList 에서 todo 데이터들을 모아놓고, TodoForm 컴포넌트에는 todo 데이터를 조작하는 함수를 내려줍니다.
  • Todo 컴포넌트에는 todo 데이터를 받아서 todo 리스트를 구성하는 HTML을 랜더링하는 역할을 하도록 합니다.
  • 그러면 위의 컴포넌트를 생성하기 위해서 src 폴더안에 components 폴더를 생성하고 TodoList.js, TodoForm.js, Todo.js 파일을 생성합니다.

3. App 컴포넌트

  • App 컴포넌트는 모든 컴포넌트를 합치는 역할을 합니다.
  • 그러므로 TodoList 컴포넌트를 import하고 return 안에 TodoList컴포넌트를 넣어줍니다.
import './App.css'
import TodoList from './components/TodoList' // 👈

function App() {
  return (
    <div className="todo-app">
      <TodoList></TodoList> // 👈
    </div>
  )
}

export default App

4. TodoList 컴포넌트

import React, { useState } from 'react'
import Todo from './Todo'
import TodoForm from './TodoForm'

const TodoList = () => {
  const [todos, setTodos] = useState([]) // 👈 useState Hook으로 todos 데이터 저장or조작

  // 할 일 "추가"하는 함수
  const addTodo = todo => {
    if (!todo.text || /^\s*$/.test(todo.text)) {
      return
    }
    const newTodos = [todo, ...todos]
    console.log(newTodos)
    setTodos(newTodos)
  }

  // 할 일 "삭제"하는 함수
  const removeTodo = id => {
    const removedArr = todos.filter(todo => todo.id !== id)
    setTodos(removedArr)
  }

  // 할 일 "수정"하는 함수
  const updateTodo = (todoId, newValue) => {
    if (!newValue.text || /^\s*$/.test(newValue.text)) {
      return
    }
    setTodos(prev => prev.map(item => (item.id === todoId ? newValue : item)))
  }

  // 할 일 "완료"하는 함수
  const completeTodo = id => {
    let updateTodos = todos.map(todo => {
      if (todo.id === id) {
        todo.isComplete = !todo.isComplete
      }
      return todo
    })
    console.log('complete')
    setTodos(updateTodos)
  }

  return (
    <div className="todo-list">
      <h1>What's The Plan For Today?</h1>
      <TodoForm onSubmit={addTodo}></TodoForm> // 👈 TodoForm 컴포넌트에는 할 일 추가하는 함수 props로 전달합니다.
      <Todo // 👈 Todo 컴포넌트에는
        todos={todos} // 할 일 데이터
        removeTodo={removeTodo} // 제거함수
        updateTodo={updateTodo} // 수정함수
        completeTodo={completeTodo} // 완료함수를 props로 전달합니다.
      ></Todo>
    </div>
  )
}

export default TodoList

5. TodoForm 컴포넌트

import React, { useState } from 'react'

const TodoForm = props => { // props 안에는 객체안에 key값으로 onSubmit 함수가 key 값으로 들어가 있습니다.
  const [input, setInput] = useState('') // input에 입력한 값을 저장하기 위해서 useState를 이용해서 input 변수를 생성합니다.

  const handleChange = e => {
    setInput(e.target.value)
  }

  const handleSubmit = e => {
    e.preventDefault()

    props.onSubmit({ // 👈 부모 컴포넌트에서 받아온 함수를 실행합니다. 그러면 인자 값이 부모 컴포넌트로 넘어갑니다. 즉, id, text 값이 넘어갑니다.
      id: Math.floor(Math.random() * 10000),
      text: input,
    })

    setInput('') // input 값을 초기화 합니다. <input value={input}/> 태그안의 text를 사라지게 만듭니다.
  }

  return (
    <form className="todo-form" onSubmit={handleSubmit}>
      {props.edit ? ( // 👈 props로 받아온 값에서 Key값으로 edit이 true이면 아래의 태그가 랜더링됩니다.
        <>
          <input
            type="text"
            placeholder="Update your item"
            name="text"
            className="todo-input edit"
            value={input}
            onChange={handleChange}
          ></input>
          <button className="todo-button edit">update</button>
        </>
      ) : ( // props.edit이 false이면 아래의 태그가 랜더링됩니다.
        <>
          <input
            type="text"
            placeholder="Add a todo"
            name="text"
            className="todo-input"
            value={input}
            onChange={handleChange}
          ></input>
          <button className="todo-button">Add</button>
        </>
      )}
    </form>
  )
}

export default TodoForm

6. Todo 컴포넌트

  • react-icons모듈을 사용해서 아이콘을 사용할 수 있습니다.
    • 모듈 설치방법
    npm install react-icons --save
    • 컴포넌트에서 사용하는 방법은 원하는 아이콘의 이름을 import 하면 됩니다.
    • 아이콘 사용은 컴포넌트처럼 사용하면 됩니다. ex) <Tidit/>
import React, { useState } from 'react'
import TodoForm from './TodoForm'
import { RiCloseCircleLine } from 'react-icons/ri' // react-icons 모듈에 있는 아이콘 가져오기
import { TiEdit } from 'react-icons/ti'

const Todo = ({ todos, removeTodo, updateTodo, completeTodo }) => { // props로 구조 분해 할당으로 받으면 변수처럼 사용할 수 있습니다.
  const [edit, setEdit] = useState({
    id: null,
    value: '',
  })

  const submitUpdate = newValue => {
    updateTodo(edit.id, newValue)

    setEdit({
      id: null,
      value: '',
    })
  }

  if (edit.id) {
    return <TodoForm edit={edit} onSubmit={submitUpdate}></TodoForm>
  }

  return (
    <div className="todo">
      {todos.map((todo, index) => (
        <div
          className={todo.isComplete ? 'todo-row complete' : 'todo-row'}
          key={index}
        >
          <div key={todo.id} onClick={() => completeTodo(todo.id)}>
            {todo.text}
          </div>
          <div className="icons">
            <RiCloseCircleLine
              className="delete-icon"
              onClick={() => removeTodo(todo.id)}
            ></RiCloseCircleLine>
            <TiEdit
              className="edit-icon"
              onClick={() => setEdit({ id: todo.id, value: todo.text })}
            ></TiEdit>
          </div>
        </div>
      ))}
    </div>
  )
}

export default Todo

7. 스타일 적용

  • App.css 파일에서 모든 컴포넌트의 스타일 적용을 할 수 있습니다.
  • 왜냐하면 App.js 가 최상위 컴포넌트인데, 여기서 App.css 파일을 import하기 때문입니다.
  • 스타일 적용은 개인마다 방법이 다양하기 때문에 원하는 스타일을 적용해 주시면 되겠습니다.
  • 저는 아래와 같이 스타일을 적용했습니다.
    1. html 태그에는 background gradient를 적용했습니다. CSS Gradient에서 원하는 스타일을 만들 수 있습니다.
    2. 제목인 h1 태그에는 animation을 적용했습니다. 네온사인 효과를 줬는데요, Codepen에서 원하는 효과를 그대로 가져와서 사용했습니다.
/* src / App.css */
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

html {
  margin-top: 70px;
  background: rgb(5, 214, 217);
  background: linear-gradient(
    90deg,
    rgba(5, 214, 217, 1) 0%,
    rgba(249, 7, 252, 1) 100%
  );
}

.todo-app {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 520px;
  min-height: 600px;
  text-align: center;
  background-color: rgba(244, 222, 222, 0.4);
  margin: auto;
  border-radius: 10px;
  padding: 2rem;
}

.todo-list {
  display: flex;
  flex-direction: column;
}

/*Neon*/
h1 {
  margin: 32px 0;
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  font-family: Monoton;
  animation: neon1 1.5s ease-in-out infinite alternate;
}

/*glow*/
@keyframes neon1 {
  from {
    text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff1177,
      0 0 70px #ff1177, 0 0 80px #ff1177, 0 0 100px #ff1177, 0 0 150px #ff1177;
  }
  to {
    text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #ff1177,
      0 0 35px #ff1177, 0 0 40px #ff1177, 0 0 50px #ff1177, 0 0 75px #ff1177;
  }
}

.todo-form {
  margin-bottom: 32px;
}

.todo-input {
  padding: 14px 32px 14px 16px;
  border-radius: 4px 0 0 4px;
  border: 2px solid rgb(194, 205, 252);
  outline: none;
  width: 320px;
  background-color: transparent;
  color: black;
}

.todo-input::placeholder {
  color: #e2e2e2;
}

.todo-button {
  padding: 16px;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
  outline: none;
  background: rgb(181, 254, 180);
  background: linear-gradient(
    90deg,
    rgba(181, 254, 180, 1) 0%,
    rgba(218, 254, 180, 1) 100%
  );
  color: darkgray;
  text-transform: uppercase;
  font-weight: 700;
}
.todo-input.edit {
  border: 2px solid #149fff;
}
.todo-button.edit {
  background: rgb(131, 58, 180);
  background: linear-gradient(
    90deg,
    rgba(131, 58, 180, 1) 0%,
    rgba(253, 29, 29, 1) 50%,
    rgba(252, 176, 69, 1) 100%
  );
  padding: 16px 22px;
}
.todo {
  overflow-y: auto;
}
.todo-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin: 4px auto;
  color: #fff;
  background: rgb(229, 189, 246);
  background: linear-gradient(
    90deg,
    rgba(229, 189, 246, 1) 0%,
    rgba(216, 222, 222, 1) 100%
  );
  padding: 15px;
  width: 90%;
  border-radius: 1rem;
}

.todo-row div:first-child {
  font-size: 1.4rem;
}

.icons {
  display: flex;
  align-items: cent;
  font-size: 24px;
  cursor: pointer;
  color: #fff;
}

.delete-icon {
  margin-right: 5px;
}

.complete {
  text-decoration: line-through;
  opacity: 0.4;
}

나가면서

  • 지금까지 React를 이용해서 todo 앱을 만들어 봤습니다.
  • 바닐라 자바스크립트로 todo 앱을 만들 때보다 훨씬 효율적인 코드로 앱을 완성시킬 수 있습니다.
  • 왜냐하면 리액트에서는 컴포넌트를 생성해서 재사용할 수 있기 때문입니다.
  • 리액트를 처음 접할 때 만나는 개념인 컴포넌트useState를 이해하면 리액트 기초를 이해할 수 있습니다.
  • 이후에 최적화를 하는 방법을 익혀서 우리가 만든 컴포넌트나 함수를 최적화시켜 나가면 됩니다.

참고

배경 이미지

배경 색상

아이콘

데이터 저장

  • MDN - localStorage
    '새싹DT 기업연계형 프론트엔드 실무 프로젝트 과정 4주차 블로그 포스팅'
profile
Here and Now. 🧗‍♂️

0개의 댓글