2020. 2. 17. 18:12

 

1. 프로젝트 생성 및 필요한 라이브러리 설치

yarn create react-app todo-app
cd todo-app
yarn add styled-components react-icons

 

2. Prettier 설정

// 프로젝트의 최상위 디렉토리에 .prettierrc 파일 생성
{
  "singleQuote": true,
  "semi": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 80
}

 

3. index.css, App.js 초기화

/*index.css 초기화*/
body {
  margin: 0;
  padding: 0;
  background-color: rgba(128,128,128,0.3);
}

 

//App.js 초기화
import React from 'react';
import './App.css';

function App() {
  return (
    <>
      <div>todo start !</div>
    </>
  );
}

export default App;

 

4. UI 구성 - 컴포넌트의 종류와 기능

  • TodoTemplate : 화면을 가운데로 정렬, 앱 타이틀 출력, children으로 내부 jsx를 props로 받아서 렌더링
  • TodoInsert : 새로운 항목 입력 및 추가, state를 통한 인풋의 상태관리
  • TodoListItem : list item 정보 출력, todo 객체를 props로 받아서 상태에 따른 (done, delete 등) 다른 스타일의 ui 적용
  • TodoList : todos 배열을 props로 받아 map을 이용해 여러개의 TodoListItem 컴포넌트로 변환

 

5. TodoTemplate UI 구현

// todo-app/src/components/StyledTodoTemplate.js
import React from 'react';
import styled from 'styled-components';

const TodoTemplate = styled.div`
  width: 512px;
  margin: 6rem auto auto auto;
  border-radius: 4px;
  overflow: hidden;

  .app-title {
    background: blueviolet;
    color: white;
    height: 4rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .content {
    background: white;
  }
`;

const StyledTodoTemplate = ({ children }) => {
  return (
    <TodoTemplate>
      <div className="app-title">TODO LIST</div>
      <div className="content">{children}</div>
    </TodoTemplate>
  );
};

export default StyledTodoTemplate;

 

6. TodoInsert UI 구현

// todo-app/src/components/StyledTodoInsert.js
import React from 'react';
import styled from 'styled-components';
import { MdAdd } from 'react-icons/md';

const Form = styled.form`
  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: yellow;
    }

    flex: 1;
  }

  button {
    /*기본 스타일 초기화*/
    background: none;
    outline: none;
    border: none;

    background: rgba(128, 128, 128, 0.2);
    color: white;
    padding: auto 1rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    cursor: pointer;
    transition: 0.1s background ease-in;
    &:hover {
      background: rgba(128, 128, 128, 0.8);
    }
  }
`;

const StyledTodoInsert = () => {
  return (
    <Form>
      <input placeholder="insert job" />
      <button type="submit">
        <MdAdd />
      </button>
    </Form>
  );
};

export default StyledTodoInsert;

 

7. TodoListItem UI 구현

// todo-app/src/components/StyledTodoListItem.js

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import styled from 'styled-components';

const TodoListItem = styled.div`
  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;
    }

    &.checkd {
      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;
  }
`;

const StyeldTodoListItem = () => {
  return (
    <TodoListItem>
      <div className="checkbox">
        <MdCheckBoxOutlineBlank />
        <div className="text">job</div>
      </div>
      <div className="remove">
        <MdRemoveCircleOutline />
      </div>
    </TodoListItem>
  );
};

export default StyeldTodoListItem;

 

8. TodoList UI 구현

// todo-app/src/components/StyledTodoList.js

import React from 'react';
import styled from 'styled-components';
import StyledTodoListItem from './StyledTodoListItem';

const TodoList = styled.div`
  min-height: 320px;
  max-height: 513px;
  overflow-y: auto;
`;

const StyledTodoLIst = () => {
  return (
    <TodoList>
      <StyledTodoListItem />
      <StyledTodoListItem />
      <StyledTodoListItem />
    </TodoList>
  );
};

export default StyledTodoLIst;

 

UI가 구현된 컴포넌트들을 App.js 에서 렌더링

// todo-app/src/App.js

import React from 'react';
import './App.css';
import StyledTodoTemplate from './components/StyledTodoTemplate';
import StyledTodoInsert from './components/StyledTodoInsert';
import StyledTodoList from './components/StyledTodoList';

function App() {
  return (
    <StyledTodoTemplate>
      <StyledTodoInsert />
      <StyledTodoList />
    </StyledTodoTemplate>
  );
}

export default App;

 

9. 기능구현1 - App에서 todos 상태 사용

// src/App.js

import React, { useState } from 'react';
import './App.css';
import StyledTodoTemplate from './components/StyledTodoTemplate';
import StyledTodoInsert from './components/StyledTodoInsert';
import StyledTodoList from './components/StyledTodoList';

function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: 'job1',
      check: true,
    },
    {
      id: 2,
      text: 'job 22',
      check: false,
    },
    {
      id: 3,
      text: 'job 333',
      check: true,
    },
  ]);

  return (
    <StyledTodoTemplate>
      <StyledTodoInsert />
      <StyledTodoList todos={todos} />
    </StyledTodoTemplate>
  );
}

export default App;

 

// src/components/StyledTodoList.js

import React from 'react';
import styled from 'styled-components';
import StyledTodoListItem from './StyledTodoListItem';

const TodoList = styled.div`
  min-height: 320px;
  max-height: 513px;
  overflow-y: auto;
`;

const StyledTodoLIst = ({ todos }) => {
  return (
    <TodoList>
      {todos.map(todo => (
        <StyledTodoListItem todo={todo} key={todo.id} />
      ))}
    </TodoList>
  );
};

export default StyledTodoLIst;

 

// src/components/StyledTodoListItem.js

import React from 'react';
import {
  MdCheckBoxOutlineBlank,
  MdCheckBox,
  MdRemoveCircleOutline,
} from 'react-icons/md';
import styled, { css } from 'styled-components';

const TodoListItem = styled.div`
  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;

      ${props =>
        props.checked &&
        css`
          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;
  }
`;

const StyeldTodoListItem = ({ todo }) => {
  const { text, check } = todo;

  return (
    <TodoListItem checked={check}>
      <div className="checkbox">
        {check ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove">
        <MdRemoveCircleOutline />
      </div>
    </TodoListItem>
  );
};

export default StyeldTodoListItem;

 

10. 기능구현2 - 항목 추가 기능

// todo-app/src/App.js

import React, { useState, useRef, useCallback } from 'react';
import './App.css';
import StyledTodoTemplate from './components/StyledTodoTemplate';
import StyledTodoInsert from './components/StyledTodoInsert';
import StyledTodoList from './components/StyledTodoList';

function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      text: 'job1',
      check: true,
    },
    {
      id: 2,
      text: 'job 22',
      check: false,
    },
    {
      id: 3,
      text: 'job 333',
      check: true,
    },
  ]);

  const nextId = useRef(4);

  const onInsert = useCallback(
    text => {
      const todo = {
        id: nextId.current,
        text,
        check: false,
      };
      setTodos(todos.concat(todo));
      nextId.current += 1;
    },
    [todos],
  );

  return (
    <StyledTodoTemplate>
      <StyledTodoInsert onInsert={onInsert} />
      <StyledTodoList todos={todos} />
    </StyledTodoTemplate>
  );
}

export default App;

 

//todo-app/src/components/StyledTodoInsert.js

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

const Form = styled.form`
  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: yellow;
    }

    flex: 1;
  }

  button {
    /*기본 스타일 초기화*/
    background: none;
    outline: none;
    border: none;

    background: rgba(128, 128, 128, 0.2);
    color: white;
    padding: auto 1rem;
    font-size: 1.5rem;
    display: flex;
    align-items: center;
    cursor: pointer;
    transition: 0.1s background ease-in;
    &:hover {
      background: rgba(128, 128, 128, 0.8);
    }
  }
`;

const StyledTodoInsert = ({ onInsert }) => {
  const [value, setValue] = useState('');

  // useCallback을 이용하여 컴포넌트가 리렌더링 될때마다 함수를 새로만드는것이 아니라 재사용
  const onChange = useCallback(e => {
    setValue(e.target.value);
  }, []);

  const onSubmit = useCallback(
    e => {
      onInsert(value);
      setValue('');
      e.preventDefault(); // submit 이벤트의 브라우저 새로고침 불허
    },
    [onInsert, value],
  );

  return (
    <Form onSubmit={onSubmit}>
      <input placeholder="insert job" value={value} onChange={onChange} />
      <button type="submit">
        <MdAdd />
      </button>
    </Form>
  );
};

export default StyledTodoInsert;

 

11. 기능구현3 - 삭제 기능 

// todo-app/src/App.js

//...

function App() {
  //... 
    
  const onRemove = useCallback(
    id => {
      setTodos(todos.filter(todo => todo.id !== id)); //filter 함수 이용
    },
    [todos],
  );

  return (
    <StyledTodoTemplate>
      <StyledTodoInsert onInsert={onInsert} />
      <StyledTodoList todos={todos} onRemove={onRemove} />
    </StyledTodoTemplate>
  );
};

export default App;

 

//todo-app/src/components/StyledTodoList.js

import React from 'react';
import styled from 'styled-components';
import StyledTodoListItem from './StyledTodoListItem';

const TodoList = styled.div`
  min-height: 320px;
  max-height: 513px;
  overflow-y: auto;
`;

const StyledTodoLIst = ({ todos, onRemove }) => {
  return (
    <TodoList>
      {todos.map(todo => (
        <StyledTodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
      ))}
    </TodoList>
  );
};

export default StyledTodoLIst;

 

//todo-app/src/components/StyledTodoListItem.js

//...

const StyeldTodoListItem = ({ todo, onRemove }) => {
  const { id, text, check } = todo;

  return (
    <TodoListItem checked={check}>
      <div className="checkbox">
        {check ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </TodoListItem>
  );
};

export default StyeldTodoListItem;

 

12. 기능구현4 - 수정기능

// todo-app/src/App.js

//...

function App() {

  //...

  const onToggle = useCallback(
    id => {
      setTodos(
        todos.map(todo =>
          todo.id === id ? { ...todo, check: !todo.check } : todo,
        ),
      );
    },
    [todos],
  );

  return (
    <StyledTodoTemplate>
      <StyledTodoInsert onInsert={onInsert} />
      <StyledTodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
    </StyledTodoTemplate>
  );
}

export default App;

 

// todo-app/src/components/StyeldTodoList.js

//...

const StyledTodoLIst = ({ todos, onRemove, onToggle }) => {
  return (
    <TodoList>
      {todos.map(todo => (
        <StyledTodoListItem
          todo={todo}
          key={todo.id}
          onRemove={onRemove}
          onToggle={onToggle}
        />
      ))}
    </TodoList>
  );
};

export default StyledTodoLIst;

 

// todo-app/src/components/StyledTodoListItem.js

//...

const StyeldTodoListItem = ({ todo, onRemove, onToggle }) => {
  const { id, text, check } = todo;

  return (
    <TodoListItem check={check}>
      <div className="checkbox" onClick={() => onToggle(id)}>
        {check ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
        <div className="text">{text}</div>
      </div>
      <div className="remove" onClick={() => onRemove(id)}>
        <MdRemoveCircleOutline />
      </div>
    </TodoListItem>
  );
};

export default StyeldTodoListItem;

 

 

 

'Javascript > React' 카테고리의 다른 글

#9 immer를 사용한 불변성(Immutability) 유지  (0) 2020.02.24
#8-1 컴포넌트 최적화  (0) 2020.02.17
#7 Component Styling  (0) 2020.02.17
#6 React Hooks  (0) 2020.02.14
#5 React Component Lifecycle  (0) 2020.02.13
Posted by yongminLEE