글 작성자: 망고좋아
반응형

 

🎯 React ToDoList - 컴포넌트 만들기

 

📝 cra설정

npx create-react-app react-todolist
npm i react-icons styled-components

 

기존 폴더에서 cra

create-react-app .

 

📝 컴포넌트 역할

📕 TodoTemplate

  • 투두리스트의 레이아웃을 설정하는 컴포넌트이다.
  • 페이지 중앙에 그림자가 적용된 흰색 박스를 보여준다.

 

📕 TodoHead

  • 오늘의 날짜와 요일을 보여주고, 앞으로 해야 할 일이 몇 개 남았는지 보여준다.

 

📕 TodoList

  • 할 일에 대한 정보가 들어있는 todos 배열을 내장 함수 map을 사용하여 여러 개의 TodoItem 컴포넌트를 렌더링 해준다.

 

📕 TodoItem

  • 각 할 일에 대한 정보를 렌더링 해주는 컴포넌트이다.
  • 좌측에 있는 원을 누르면 할 일의 완료 여부를 toggle 할 수 있다.
  • 할 일이 완료됐을 땐 좌측에 체크가 나타나고 텍스트의 색상이 연해진다.
  • 마우스를 올리면 휴지통 아이콘이 나타나고 이를 누르면 항목이 삭제된다.

 

📕 TodoCreate

  • 새로운 할 일을 등록할 수 있게 해주는 컴포넌트이다.
  • Todo Template의 하단부에 초록색 원 버튼을 렌더링 해주고, 이를 클릭하면 할 일을 입력할 수 있는 폼이 나타난다.
  • 버튼을 다시 누르면 폼이 사라진다.

 

📝 페이지에 회색 배경 색상 적용 (feat. styled-components)

  • styled-components에서 특정 컴포넌트를 만들어서 스타일링하는 게 아니라 글로벌 스타일을 추가하고 싶을 때는 createGlobalStyle을 사용한다. 이 함수를 사용하면 컴포넌트가 만들어지는데, 이 컴포넌트를 렌더링 하면 된다.

 

🛠 App.js

import React from 'react';
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    background: #e9ecef;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <div>안녕하세요</div>
    </>
  );
}

export default App;

 

📝 TodoTemplate 만들기

  • 중앙에 정렬된 흰색 박스 만들기

 

🛠 components/TodoTemplate.js

import React from "react";
import styled from "styled-components";

const TodoTemplateBlock = styled.div `
    width: 512px;
    height: 768px;

    position: relative;
    background: white;
    border-radius: 16px;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.04);

    margin: 0 auto;

    margin-top: 96px;
    margin-bottom: 32px;
    display: flex;
    flex-direction: column;
`;


function TodoTemplate({ children }) {
    return <TodoTemplateBlock>{children}</TodoTemplateBlock>

}

export default TodoTemplate;

 

🛠 App.js

import React from 'react';
import { createGlobalStyle } from 'styled-components';
import TodoTemplate from './components/TodoTemplate';

const GlobalStyle = createGlobalStyle`
  body {
    background: #e9ecef;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <TodoTemplate>안녕하세요</TodoTemplate>
    </>
  );
}

export default App;
  • App 랜더링

 

 

📝 TodoHead 만들기

  • 오늘의 날짜, 요일, 남은 할 일 개수를 보여주기

 

🛠 components/TodoHead.js

import React from "react";
import styled from "styled-components";

const TodoHeadBlock = styled.div`
    padding: 48px 32px 24px 32px;
    border-bottom: 1px solid #e9ecef;
    h1 {
        margin: 0px;
        font-size: 36px;
        color: 343a40;
    }
    .day {
    margin-top: 4px;
    color: #868e96;
    font-size: 21px;
  }
  .tasks-left {
    color: #20c997;
    font-size: 18px;
    margin-top: 40px;
    font-weight: bold;
  }
`;

function TodoHead() {
    return (
        <TodoHeadBlock>
            <h1>2021년 10월 5일</h1>
            <div className="day">화요일</div>
            <div className="tasks-left">할 일 2개 남음</div>
        </TodoHeadBlock>
    )
}

export default TodoHead;
  • TodoHeadBlock 안에 들어 있는 내용들에 대해서 컴포넌트를 만드는 대신 일반 html태그를 사용하고 CSS Selector를 사용하여 스타일 적용
  • 기능적으로 게 중요하지 않는 내용이면 CSS Selector를 사용하는 것도 좋은 방법이다.

 

🛠 App.js

import React from 'react';
import { createGlobalStyle } from 'styled-components';
import TodoTemplate from './components/TodoTemplate';
import TodoHead from './components/TodoHead';

const GlobalStyle = createGlobalStyle`
  body {
    background: #e9ecef;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <TodoTemplate>
        <TodoHead />
      </TodoTemplate>
    </>
  );
}

export default App;

 

📝 TodoList 만들기

  • 여러 개의 할 일 항목을 보여주게 될 TodoList 만들기

 

🛠 components/TodoList.js

import React from "react";
import styled from "styled-components";

const TodoListBlock = styled.div`
    flex: 1;
    padding: 20px 32px;
    padding-bottom: 48px;
    overflow-x: auto;
    background: gray; /* 임시 스타일*/
`;

function TodoList() {
    return <TodoListBlock>TodoList</TodoListBlock>
}

export default TodoList;
  • flex: 1;을 설정해서 자신이 차지할 수 있는 영역을 꽉 채우도록 설정

 

 

📝 TodoItem 만들기

  • 각 할 일 항목들을 보여주는 TodoItem 컴포넌트 만들기
  • react-icons에서 MdDone과 MdDelete 아이콘을 사용

 

🛠 TodoItem.js

import React from 'react';
import styled, { css } from 'styled-components';
import { MdDone, MdDelete } from 'react-icons/md';

const Remove = styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    color: #dee2e6;
    font-size: 24px;
    cursor: pointer;
    &:hover {
        color: #ff6b6b;
    }
    display: none;
`;

const TodoItemBlock = styled.div`
    display: flex;
    align-items: center;
    padding-top: 12px;
    padding-bottom: 12px;
    &:hover {
        ${Remove} {
            display: initial;
        }
    }
`;

const CheckCircle = styled.div`
    width: 32px;
    height: 32px;
    border-radius: 16px;
    border: 1px solid #ced4da;
    font-size: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    cursor: pointer;
    ${props =>
        props.done &&
        css`
            border: 1px solid #38d9a9;
            color: #38d9a9;
        `}
`;

const Text = styled.div`
    flex: 1;
    font-size: 21px;
    color: #495057;
    ${props =>
        props.done &&
        css`
            color: #ced4da;
        `}
`;

function TodoItem({ id, done, text }) {
    return (
        <TodoItemBlock>
            <CheckCircle done={done}>{done && <MdDone />}</CheckCircle>
            <Text done={done}>{text}</Text>
            <Remove>
                <MdDelete />
            </Remove>
        </TodoItemBlock>
    );
}

export default TodoItem;
  • TodoItemBlock에서 Component Selector라는 기능을 사용
    • TodoItemBlock 위에 커서가 있을 때, Remove 컴포넌트를 보여주라는 의미를 가지고 있다.

 

🛠 TodoList.js

import React from "react";
import styled from "styled-components";
import TodoItem from "./TodoItem";

const TodoListBlock = styled.div`
    flex: 1;
    padding: 20px 32px;
    padding-bottom: 48px;
    overflow-x: auto;
`;

function TodoList() {
    return (
        <TodoListBlock>
            <TodoItem text="프로젝트 생성하기" done={true} />
            <TodoItem text="컴포넌트 스타일링 하기" done={true} />
            <TodoItem text="Context 만들기" done={false} />
            <TodoItem text="기능 구현하기" done={false} />
        </TodoListBlock>
    );
}

export default TodoList;
  • TodoItem 컴포넌트를 TodoList에서 랜더링 해주기

 

 

📝 TodoCreate 만들기

  • 새로운 항목을 등록할 수 있는 컴포넌트
  • useState를 사용하여 토글 할 수 있는 open 값을 관리하며, 이 값이 true일 때에는 아이콘을 45도 돌려서 X 모양이 보이게 한 후, 버튼 색상을 빨간색으로 바꿔준다.
  • 그리고 할 일을 입력할 수 있는 폼도 보여준다.

 

🛠 TodoCreate.js

import React, { useState } from 'react';
import styled, { css } from 'styled-components';
import { MdAdd } from 'react-icons/md';

const CircleButton = styled.button`
  background: #38d9a9;
  &:hover {
    background: #63e6be;
  }
  &:active {
    background: #20c997;
  }

  z-index: 5;
  cursor: pointer;
  width: 80px;
  height: 80px;
  display: block;
  align-items: center;
  justify-content: center;
  font-size: 60px;
  position: absolute;
  left: 50%;
  bottom: 0px;
  transform: translate(-50%, 50%);
  color: white;
  border-radius: 50%;
  border: none;
  outline: none;
  display: flex;
  align-items: center;
  justify-content: center;

  transition: 0.125s all ease-in;
  ${props =>
    props.open &&
    css`
      background: #ff6b6b;
      &:hover {
        background: #ff8787;
      }
      &:active {
        background: #fa5252;
      }
      transform: translate(-50%, 50%) rotate(45deg);
    `}
`;

const InsertFormPositioner = styled.div`
  width: 100%;
  bottom: 0;
  left: 0;
  position: absolute;
`;

const InsertForm = styled.form`
  background: #f8f9fa;
  padding-left: 32px;
  padding-top: 32px;
  padding-right: 32px;
  padding-bottom: 72px;

  border-bottom-left-radius: 16px;
  border-bottom-right-radius: 16px;
  border-top: 1px solid #e9ecef;
`;

const Input = styled.input`
  padding: 12px;
  border-radius: 4px;
  border: 1px solid #dee2e6;
  width: 100%;
  outline: none;
  font-size: 18px;
  box-sizing: border-box;
`;

function TodoCreate() {
  const [open, setOpen] = useState(false);

  const onToggle = () => setOpen(!open);

  return (
    <>
      {open && (
        <InsertFormPositioner>
          <InsertForm>
            <Input autoFocus placeholder="할 일을 입력 후, Enter 를 누르세요" />
          </InsertForm>
        </InsertFormPositioner>
      )}
      <CircleButton onClick={onToggle} open={open}>
        <MdAdd />
      </CircleButton>
    </>
  );
}

export default TodoCreate;
  • const [open, setOpen] = useState(false);에서 open의 초기값으로 false를 설정해줬다.
  • 따라서 TodoCreate() 함수에서
    • open이 true면 InsertFormPositioner
    • false면 CircleButton을 보여준다.
${props =>
    props.open &&
    css`
        ...
    `}
  • open이라는 props가 존재할 때(true) css로 정의된 스타일이 적용된다.

 

🛠 App.js

import React from 'react';
import { createGlobalStyle } from 'styled-components';
import TodoTemplate from './components/TodoTemplate';
import TodoHead from './components/TodoHead';
import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';

const GlobalStyle = createGlobalStyle`
  body {
    background: #e9ecef;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <TodoTemplate>
        <TodoHead />
        <TodoList />
        <TodoCreate />
      </TodoTemplate>
    </>
  );
}

export default App;

 

🏷 요약

  • styled-components에서 &:sth$는 자기 선택자이다.
  • styled-components는 컴포넌트의 props를 전달받아 사용하는 것이 가능하다.
    • && 연산자를 사용하면 해당 props가 존재하는 경우에는 정의한 css가 적용될 수 있도록 할 수 있다.
  • 리액트에 다루는 데이터는 porps와 state로 나뉜다.
    • porps란 properties로 컴포넌트 속성을 설정할 때 사용한다.
    • 자식 컴포넌트에서는 props를 받아오기만 하고, 받아온 props를 직접 수정할 수는 없다. 자식 컴포넌트에서는 props는 읽기 전용 데이터이다.
    • state는 컴포넌트 내부에서 선언하며 내부에서 값을 변경할 수 있다.

 

📌 참고

반응형