잿꽃's posting Garden

REACT Todolist 만들기 - 2 본문

WEB/REACT

REACT Todolist 만들기 - 2

잿꽃 2022. 4. 29. 02:35

REACT에서 Todolist를 만들어봤습니다.

2022.04.28 - [WEB] - REACT Todolist 만들기 - 1

 

REACT Todolist 만들기 - 1

REACT에서 Todolist를 만들어봤습니다. 1. UI틀 만들기 2. styled-components로 꾸미고 간단한 속성 부여하기 3장. 멋진 투두리스트 만들기 · GitBook (vlpt.us) 3장. 멋진 투두리스트 만들기 · GitBook 3장. 멋..

flowerofashes.tistory.com

 

 

3. 기능 구현하기

마침 강의 평가가 있어서 강의에서 배운 내용을 토대로 기능을 구현해 보았습니다.

 

 

1. app.js와 TodoListTotal.js의 할 일을 수정하기

기존에 TodoListTotal.js에 직접 입력했던 내용을 상태관리가 가능하게 하기 위한 목적

 

- app.js내에서 할 일을 작성해주고 상태관리가 가능한 useState()를 활용한다.

- TodoListTotal의 속성 값으로 useState()의 기본 상태인 todoList를 전달한다.

/////////////////app.js/////////////////
import React, {useRef, useState, useCallback} from 'react';
import TodoListHeader from './Components/TodoListHeader';
import TodoListWrap from './Components/TodoListWrap';
import TodoListTotal from './Components/TodoListTotal';
import TodoListAdd from './Components/TodoListAdd';

const App = () => {
  // todolist의 내용 기본 셋팅
  const [todoList, setTodoList] = useState([
    { id: 1, text: 'UI 구성 생각하기', check : true},
    { id: 2, text: '각각의 Components 만들기', check : true},
    { id: 3, text: 'Styled-Components로 꾸미기', check : true},
    { id: 4, text: '기능 구현하기', check : false},
  ]);

  return (
    <div>
      <TodoListWrap>
          <TodoListHeader></TodoListHeader>
          <TodoListTotal todoList={todoList}></TodoListTotal>
          <TodoListAdd></TodoListAdd>
      </TodoListWrap>
    </div>
  );
};

export default App;

 

- TodoListTotal.js에서 전달받은 todoList를 map함수를 통해 TodoListItem 컴포넌트에 그대로 전달하고 key값을 id값으로 이용한다.

////////////////////TodoListTotal.js////////////////////
import React from 'react';
import  styled  from 'styled-components';
import TodoListItem from './TodoListItem';

// 전체 할 일을 감싸는 용도
// app.js에서 받은 속성을 TodoListItem에 그대로 전달한다.
const TodoListTotal = ({todoList}) => {
    return (
        <TodoTotal>
            {todoList.map(todo=>(<TodoListItem todo={todo} key={todo.id}></TodoListItem>))}
        </TodoTotal>
    );
};

const TodoTotal = styled.div`
    display: flex;
    flex: 1;
    flex-direction: column;
    margin-top: 10px;
`;

export default TodoListTotal;

 

 

2. TodoListHeader.js에서 남은 할 일의 개수 세기

app.js에서 작성된 list중에서 check가 false인 값을 세기 위한 목적

 

- app.js에서 TodoListHeader.js로 todoList를 전달한다.

/////////////////app.js/////////////////
import React, {useRef, useState, useCallback} from 'react';
import TodoListHeader from './Components/TodoListHeader';
import TodoListWrap from './Components/TodoListWrap';
import TodoListTotal from './Components/TodoListTotal';
import TodoListAdd from './Components/TodoListAdd';

const App = () => {
  // todolist의 내용 기본 셋팅
  const [todoList, setTodoList] = useState([
    { id: 1, text: 'UI 구성 생각하기', check : true},
    { id: 2, text: '각각의 Components 만들기', check : true},
    { id: 3, text: 'Styled-Components로 꾸미기', check : true},
    { id: 4, text: '기능 구현하기', check : false},
  ]);

  return (
    <div>
      <TodoListWrap>
          <TodoListHeader todoList={todoList}></TodoListHeader>
          <TodoListTotal todoList={todoList}></TodoListTotal>
          <TodoListAdd></TodoListAdd>
      </TodoListWrap>
    </div>
  );
};

export default App;

 

- 전달 받은 todoList를 filter()함수를 통해 전달받은 list중에서 check의 값이 false인 값만 변수에 담는다.

const nonCheck = todoList.filter(todo => !todo.check)라고도 쓸 수 있다. true인 값만 반환되기 때문이다.

- p태그에 필터로 걸러진 배열의 길이를 입력한다.

/////////////////TodoListHeader.js/////////////////
import React, {useEffect, useState} from 'react';
import  styled  from 'styled-components';

// 오늘의 날짜와 남은 할일을 보여주는 용도
const TodoListHeader = ({todoList}) => {
    // todoList에서 각 요소의 check가 true가 아닌 것만 반환한다.
    const nonCheck = todoList.filter(todo=> todo.check !== true)
    
    // 시간의 상태 변화를 렌더링 할 useState
    const [time, setTime] = useState(new Date())

    // 문서가 마운트 되었을 때 실행된다.
    // setInterval함수로 1초마다 새로운 시간을 받는다.
    useEffect(()=>{
        setInterval(()=>{
            setTime(new Date())
        },1000)
    },[])

    return (
        <TodoHeader>
            <h1>{time.toLocaleDateString()}</h1>
            <p>할일이 {nonCheck.length}개 남았습니다.</p>
        </TodoHeader>
    );
};

const TodoHeader = styled.div`
    width: 100%;
    padding: 20px;
    border-bottom: 1px solid #ccc;
    h1{
        margin-top: 10px;
    }
    p{
        margin-top: 1rem;
        color: #219ebc;
    }
`;

export default TodoListHeader;

 

 

3. 새로운 할 일 기능을 추가하기

input에서 입력된 내용을 새 배열에 저장하고 기존 배열과 합치기 위한 목적

 

- 업데이트 되어도 값이 변하지 않으면서 화면에 변화된 값이 나타나지 않아도 되는 id값을 useRef()를 활용하여 다음 id값을 생성

- 리스트를 추가할 때 todoList에 관련해서 렌더링될 때만 작업이 수행되도록 useCallback()을 사용.

texts에는 TodoListAdd.js에서 input값으로 전달받은 값이 들어갈 예정

- setTodoList에 기존의 list에 concat()을 활용하여 추가할 list를 합친다.

- useRef()의 현재 값을 1증가시킨다( nextId.current++과 동일 )

- 만든 변수를 TodoListAdd.js로 속성 값으로 전달.

/////////////////app.js/////////////////
import React, {useRef, useState, useCallback} from 'react';
import TodoListHeader from './Components/TodoListHeader';
import TodoListWrap from './Components/TodoListWrap';
import TodoListTotal from './Components/TodoListTotal';
import TodoListAdd from './Components/TodoListAdd';

const App = () => {
  // todolist의 내용 기본 셋팅
  const [todoList, setTodoList] = useState([
    { id: 1, text: 'UI 구성 생각하기', check : true},
    { id: 2, text: '각각의 Components 만들기', check : true},
    { id: 3, text: 'Styled-Components로 꾸미기', check : true},
    { id: 4, text: '기능 구현하기', check : false},
  ]);

  // 다음 id값을 지정하기 위함
  const nextId = useRef(5)

  // 새로운 할 일 추가 기능
  // useCallback을 이용하여 todolist에 관해서만 렌더링
  // 새로운 list를 만들어(newList) 기존 리스트(setTodoList)와 합친다.
  const addList = useCallback(texts =>{
    const newList = {
      id : nextId.current,
      text : texts,
      check : false
    }
    setTodoList(todoList.concat(newList))
    nextId.current+= 1
  }, [todoList])

  return (
    <div>
      <TodoListWrap>
          <TodoListHeader todoList={todoList}></TodoListHeader>
          <TodoListTotal todoList={todoList}></TodoListTotal>
          <TodoListAdd addList={addList}></TodoListAdd>
      </TodoListWrap>
    </div>
  );
};

export default App;

 

- TodoListAdd.js 전달 된 addList를 인자에 입력

- addList함수를 이용하기 전 input을 감지할 내용이 필요할 것이다.

 

- useState()를 활용하여 input의 기본 value의 값을 지정함. inputText가 input의 value값을 가지고 있으므로 '할 일을 입력해보세요'라는 문구를 입력해도 좋을 듯하다.

- setInputText에 해당하는 value의 값을 넣는다.

- input에 onChange이벤트와 value의 값에 해당하는 내용을 각각 넣어준다.

 

- app.js에서 가져온 addList에 인자 texts에 값을 넣어야 하는데 이 부분은 'text :     '에 해당하므로 inputText를 넣어준다. 추가 후 input상자에 value값이 남아있으면 안 되므로 setInputText를 이용해 초기화한다.

 

- input상자에 onSubmit이벤트에 만든 함수를 추가한다.  

* onSubmit 이벤트를 이용하기 위해서는 해당 박스의 속성이 form의 요소이어야 한다. styled-components에서 지정한 div를 form으로 수정하였다. (const TodoAdd = styled.form` css 내용 `;)

/////////////////TodoListAdd.js/////////////////
import React, {useCallback, useState} from 'react';
import styled from 'styled-components';
import {IoMdAddCircle} from 'react-icons/io';
import {css} from 'styled-components'

// 할 일을 추가하는 용도
const TodoListAdd = ({addList}) => {
    // 추가 버튼에 따라 입력창이 열리고 닫히는 효과
    const [open, setOpen] = useState(false);

    const [inputText, setInputText] = useState('');
    // 입력한 값을 감지하는 함수
    const onChange = useCallback((e)=>{
        setInputText(e.target.value)
    },[])

    // 감지한 내용을 리스트에 추가하는 함수
    const onSubmit = useCallback((e)=>{
        addList(inputText)
        setInputText('')
        e.preventDefault()
    },[addList, inputText])

    return (
        <TodoAdd onSubmit={onSubmit}>
            { open && <InputBox>
                <input type="text" onChange={onChange} value={inputText}></input>
            </InputBox>}
            <TodoAddBtn onClick={()=>setOpen(!open)} open={open}></TodoAddBtn>
        </TodoAdd>
    );
};

const TodoAdd = styled.form`
    width: 100%;
    height: 120px;
    position: relative;
`;

const InputBox =styled.div`
    width: 100%;
    height: 120px;
    background: #ccc;
    text-align: center;
    input{
        width: 60%;
        height: 30px;
        margin-top: 15px;
    }
`;

const TodoAddBtn = styled(IoMdAddCircle)`
    position: absolute;
    left: 0;
    right: 0;
    margin: 0 auto;
    bottom: 5px;
    font-size: 4rem;
    color: #219ebc;
    cursor: pointer;
    &:hover{
        color: #63e6be;
    }
    ${(props)=> props.open &&
    css`
    &:hover{
        color: #ff8787;
    }
    color: #ff8787; 
    transform: rotate(45deg)
    `}
`;
export default TodoListAdd;

 

 

4. 할 일을 삭제하는 기능 구현하기

list에서 선택한 요소가 리스트 중에 일치하는 것을 거르는 목적

 

- filter()를 이용해 선택한 아이디가 기존의 아이디와 일치하지 않는 것만 setTodoList에 담는다.

- 만든 변수를 TodoListTotal.js에 전달. 이 동작은 TodoListTotal.js이 아닌 TodoListItem.js에서 실행되어야 하므로 중간 전달 역할을 한다. 

/////////////////app.js/////////////////
import React, {useRef, useState, useCallback} from 'react';
import TodoListHeader from './Components/TodoListHeader';
import TodoListWrap from './Components/TodoListWrap';
import TodoListTotal from './Components/TodoListTotal';
import TodoListAdd from './Components/TodoListAdd';

const App = () => {
  // todolist의 내용 기본 셋팅
  const [todoList, setTodoList] = useState([
    { id: 1, text: 'UI 구성 생각하기', check : true},
    { id: 2, text: '각각의 Components 만들기', check : true},
    { id: 3, text: 'Styled-Components로 꾸미기', check : true},
    { id: 4, text: '기능 구현하기', check : false},
  ]);

  // 다음 id값을 지정하기 위함
  const nextId = useRef(5)

  // 새로운 할 일 추가 기능
  // useCallback을 이용하여 todolist에 관해서만 렌더링
  // 새로운 list를 만들어(newList) 기존 리스트(setTodoList)와 합친다.
  const addList = useCallback(texts =>{
    const newList = {
      id : nextId.current,
      text : texts,
      check : false
    }
    setTodoList(todoList.concat(newList))
    nextId.current++
  }, [todoList])

  // 할일 삭제하는 기능
  // setTodoList에 todoList중 현재 id와 요소의 id가 같은지 각각 확인 후 같지 않은 것만 들어간다.  
  const onRemove = useCallback(id => {
      setTodoList(todoList.filter((todo) => todo.id !== id))
    },[todoList]
  );

  return (
    <div>
      <TodoListWrap>
          <TodoListHeader todoList={todoList}></TodoListHeader>
          <TodoListTotal todoList={todoList} onRemove={onRemove}></TodoListTotal>
          <TodoListAdd addList={addList}></TodoListAdd>
      </TodoListWrap>
    </div>
  );
};

export default App;

 

- TodoListTotal.js에서 TodoListItem.js로 전달한다.

/////////////////TodoListTotal.js/////////////////
import React from 'react';
import  styled  from 'styled-components';
import TodoListItem from './TodoListItem';

// 전체 할 일을 감싸는 용도
// app.js에서 받은 속성을 TodoListItem에 그대로 전달한다.
const TodoListTotal = ({todoList, onRemove}) => {
    return (
        <TodoTotal>
            {todoList.map(todo=>(<TodoListItem todo={todo} key={todo.id} onRemove={onRemove}></TodoListItem>))}
        </TodoTotal>
    );
};

const TodoTotal = styled.div`
    display: flex;
    flex: 1;
    flex-direction: column;
    margin-top: 10px;
`;

export default TodoListTotal;

 

- 전달받은 onRemove는 버리는 아이콘을 눌렀을 때 실행되어야 하므로 onClick이벤트에 넣어주고 인자는 비구조할당으로 꺼내온 id를 입력한다.

/////////////////TodoListItem.js/////////////////
import React from 'react';
import { BsCircle, BsCheckCircle, BsDashCircleFill } from 'react-icons/bs'
import  styled  from 'styled-components';
import {css} from 'styled-components'

// 낱개의 할 일을 나타내는 용도
// app.js > TodoListTotal.js에서 전달받은 인자를 이용한다.
const TodoListItem = ({todo, onRemove}) => {
    // todo의 key값을 비구조할당으로 꺼내서 이용한다.
    const {id, text, check} = todo;
    return (
        <TodoItem>
            { check ? <CheckBtn}></CheckBtn> : <CircleBtn}></CircleBtn>}
            <TodoText check={check}>{text}</TodoText>
            <RemoveBtn onClick={() => onRemove(id)}>버리기</RemoveBtn>
        </TodoItem>
    );
};

const CheckBtn = styled(BsCheckCircle)`
    font-size: 1.5rem;
    color: #219ebc;
    cursor: pointer;
`;
const CircleBtn = styled(BsCircle)`
    font-size: 1.5rem;
    color: #888;
    cursor: pointer;
`;
const RemoveBtn = styled(BsDashCircleFill)`
    font-size: 1.5rem;
    color: #ff8787;
    display: none;
    cursor: pointer;
`;
const TodoItem = styled.div`
    width: 100%;
    padding: 10px 20px;
    display: flex;
    align-items: center;
    &:hover {
        background: #ddd;
        ${RemoveBtn}{
            display: initial;
        }
    }
`;
const TodoText = styled.div`
    flex: 1;
    padding-left: 10px;
    color: #333;
    ${props=> props.check && 
    css` 
    color: #888; 
    text-decoration: line-through;
    `}
`;


export default TodoListItem;

 

 

5. 체크 아이콘을 누르면 아이콘이 바뀌는 기능 구현하기

list에서 선택한 요소가 리스트와 일치하는지 확인하고 일치하는 요소의 check 상태를 변경시키는 목적

 

- map()

2022.04.28 - [WEB] - REACT Todolist 만들기 - 1

를 이용해 모든 값을 return하지만 그중에 일치하는 요소만 check의 상태를 !를 사용해 변경(check가 아닌 key, value값은 전개연산자로 그대로 전달)

- 4번의 내용과 동일하게 TodoListTotal 컴포넌트에 전달하고 이것은 TodoListItem까지 전달할 수 있게 한다.

/////////////////app.js/////////////////
import React, {useRef, useState, useCallback} from 'react';
import TodoListHeader from './Components/TodoListHeader';
import TodoListWrap from './Components/TodoListWrap';
import TodoListTotal from './Components/TodoListTotal';
import TodoListAdd from './Components/TodoListAdd';

const App = () => {
  // todolist의 내용 기본 셋팅
  const [todoList, setTodoList] = useState([
    { id: 1, text: 'UI 구성 생각하기', check : true},
    { id: 2, text: '각각의 Components 만들기', check : true},
    { id: 3, text: 'Styled-Components로 꾸미기', check : true},
    { id: 4, text: '기능 구현하기', check : false},
  ]);

  // 다음 id값을 지정하기 위함
  const nextId = useRef(5)

  // 새로운 할 일 추가 기능
  // useCallback을 이용하여 todolist에 관해서만 렌더링
  // 새로운 list를 만들어(newList) 기존 리스트(setTodoList)와 합친다.
  const addList = useCallback(texts =>{
    const newList = {
      id : nextId.current,
      text : texts,
      check : false
    }
    setTodoList(todoList.concat(newList))
    nextId.current++
  }, [todoList])

  // 할일 삭제하는 기능
  // setTodoList에 todoList중 현재 id와 요소의 id가 같은지 각각 확인 후 같지 않은 것만 들어간다.  
  const onRemove = useCallback(id => {
      setTodoList(todoList.filter((todo) => todo.id !== id))
    },[todoList]
  );

  // 체크 버튼 토글의 기능
  // 현재의 id와 요소의 id가 같은지 각각 확인후 같으면 check를 제외한 모든 요소는 동일하게 사용하고 check는 기존의 상태의 반대가 되게 한다. id가 다르면 동일 값을 사용한다.
  const onToggle = useCallback(id => {
    setTodoList(todoList.map(todo => todo.id === id ? {...todo, check : !todo.check} : todo))
  },[todoList])

  return (
    <div>
      <TodoListWrap>
          <TodoListHeader todoList={todoList}></TodoListHeader>
          <TodoListTotal todoList={todoList}  onRemove={onRemove} onToggle={onToggle}></TodoListTotal>
          <TodoListAdd addList={addList}></TodoListAdd>
      </TodoListWrap>
    </div>
  );
};

export default App;

 

- TodoListTotal.js에서 TodoListItem.js로 전달한다.

 

/////////////////TodoListTotal.js/////////////////
import React from 'react';
import  styled  from 'styled-components';
import TodoListItem from './TodoListItem';

// 전체 할 일을 감싸는 용도
// app.js에서 받은 속성을 TodoListItem에 그대로 전달한다.
const TodoListTotal = ({todoList, onRemove, onToggle}) => {
    return (
        <TodoTotal>
            {todoList.map(todo=>(<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} onToggle={onToggle}></TodoListItem>))}
        </TodoTotal>
    );
};

const TodoTotal = styled.div`
    display: flex;
    flex: 1;
    flex-direction: column;
    margin-top: 10px;
`;

export default TodoListTotal;

 

- 아이콘을 누르면 check의 상태가 바뀌어야 하니 onClick이벤트에 작성하고 인자 값으로는 todo에서 꺼낸 id를 사용한다(4번과 동일 내용)

/////////////////TodoListItem.js/////////////////
import React from 'react';
import { BsCircle, BsCheckCircle, BsDashCircleFill } from 'react-icons/bs'
import  styled  from 'styled-components';
import {css} from 'styled-components'

// 낱개의 할 일을 나타내는 용도
// app.js > TodoListTotal.js에서 전달받은 인자를 이용한다.
const TodoListItem = ({todo, onRemove, onToggle}) => {
    // todo의 key값을 비구조할당으로 꺼내서 이용한다.
    const {id, text, check} = todo;
    return (
        <TodoItem>
            { check ? <CheckBtn onClick={()=>onToggle(id)}></CheckBtn> : <CircleBtn onClick={()=>onToggle(id)}></CircleBtn>}
            <TodoText check={check}>{text}</TodoText>
            <RemoveBtn onClick={() => onRemove(id)}>버리기</RemoveBtn>
        </TodoItem>
    );
};

const CheckBtn = styled(BsCheckCircle)`
    font-size: 1.5rem;
    color: #219ebc;
    cursor: pointer;
`;
const CircleBtn = styled(BsCircle)`
    font-size: 1.5rem;
    color: #888;
    cursor: pointer;
`;
const RemoveBtn = styled(BsDashCircleFill)`
    font-size: 1.5rem;
    color: #ff8787;
    display: none;
    cursor: pointer;
`;
const TodoItem = styled.div`
    width: 100%;
    padding: 10px 20px;
    display: flex;
    align-items: center;
    &:hover {
        background: #ddd;
        ${RemoveBtn}{
            display: initial;
        }
    }
`;
const TodoText = styled.div`
    flex: 1;
    padding-left: 10px;
    color: #333;
    ${props=> props.check && 
    css` 
    color: #888; 
    text-decoration: line-through;
    `}
`;


export default TodoListItem;

 

 

 

완성

 

완성된 내용은 아래의 사이트에서 확인이 가능합니다.

React App (ssoa1111.github.io)

 

React App

 

ssoa1111.github.io

 

728x90
Comments