본문 바로가기
매일 해내는 개발/Develog

[Develog] 타입스크립트로 TodoList만들기2

by 해야지 2023. 1. 20.
반응형

어제에 이어서 추가 진행된 부분

1) 삭제 기능
2) 완료 기능
3) 취소 기능
4) 상세페이지 토글창 구현

삭제까지는 유데미 영상을 보면서 클론코딩으로 진행했고
이후 기능은

 

깃허브링크

 

GitHub - jeLee94/type_prac

Contribute to jeLee94/type_prac development by creating an account on GitHub.

github.com

 

0. 이미지

 

1. 삭제 기능

TodoItems.tsx는 Todos에서 RemoveItem을 받아온다.
Todos.tsx는 App.tsx에서 onDeleteHandler를 받아온다.
App.tsx에서는 state를 set하도록 함수를 정의한다.

TodoItems.tsx
Todo.tsx
App.tsx

 

2. 완료/취소 기능

흐름은 위와 같고 App.tsx에서 정의하는 함수만 다르다
onDoneHandler에 이벤트가 발생하면 매개변수로 전달받은 todoItem의 isDone이 토글된다.
true일 때는 완료가 된 상태이므로
todos에는 현재 완료 버튼을 누른 todo를 제외한 나머지만 남게하고
doneTodos에는 이전에 doneTodos의 데이터와 현재 todo를 합친다.
false일 때는 done -> todo로 취소를 하는 상황이므로
true일 때와 반대로 작성한다.

 

3. 상세 페이지 구현

todo 컴포넌트 하나를 누르면 우측에 화면이 토글되도록 한다.
todo 하나를 눌러 활성화 된 상태에서 다른 todo를 누르면 토글되지 않고 다른 todo의 내용으로 변환되도록 한다.

 

4. 코드

App.tsx

function App() {
  const [allList, setAllList] = useState<Todo[]>([]); //상세 토글 창을 위해 전체 리스트 생성
  const [todos, setTodos] = useState<Todo[]>([]);
  const [doneTodos, setDoneTodos] = useState<Todo[]>([]);
  const [isDetailShown, setIsDetailShown] = useState(false);
  const [onClickId, setOnClickId] = useState('');

  const addTodoHandler = (todoText: string) => {
    const newTodo = new Todo(todoText);
    setTodos((prevTodos) => {
      return prevTodos.concat(newTodo);
    });
    setAllList((prevTodos) => {
      return prevTodos.concat(newTodo);
    });
  };

  const onDeleteHandler = (todoId: string) => {
    if (window.confirm('정말 삭제하시겠습니까?')) {
      setTodos((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoId);
      });
      setDoneTodos((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoId);
      });
      setAllList((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoId);
      });
    }
  };

  const onDoneHandler = (todoItem: Todo) => {
    todoItem.isDone = !todoItem.isDone;

    if (todoItem.isDone) {
      setTodos((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoItem.id);
      });
      setDoneTodos((prevTodos) => {
        return prevTodos.concat(
          todos.filter((todo) => todo.id === todoItem.id)
        );
      });
    } else {
      setTodos((prevTodos) => {
        return prevTodos.concat(todoItem);
      });
      setDoneTodos((prevTodos) => {
        return prevTodos.filter((todo) => todo.id !== todoItem.id);
      });
    }
  };

  const onDetailHandler = (todoItem: Todo) => {
    console.log('클릭!');
    !isDetailShown ? setIsDetailShown(true) : setOnClickId(todoItem.id); //디테일 창이 false이면 보여주고 보여주고 있으면 내용 변경
    todoItem.id === onClickId && isDetailShown === true
      ? setIsDetailShown(false)
      : setOnClickId(todoItem.id);
    setOnClickId(todoItem.id);
  };

  return (
    <Div>
      <MainDiv>
        <AddTodo onAddTodo={addTodoHandler} />
        <Todos
          todoItems={todos}
          doneItems={doneTodos}
          onDeleteHandler={onDeleteHandler}
          onDoneHandler={onDoneHandler}
          onDetailHandler={onDetailHandler}
        />
      </MainDiv>
      {/* 상세페이지 토글기능 */}
      {isDetailShown
        ? allList.map((todo) =>
            todo.id === onClickId ? <DetailPage todo={todo} /> : null
          )
        : null}
    </Div>
  );
}

 

Todos.tsx

import React from 'react';
import styled from 'styled-components';
import Todo from '../models/todo';
import TodoItem from './TodoItems';
// import DoneItem from './DoneItem';

const Todos: React.FC<{
  todoItems: Todo[];
  doneItems: Todo[];
  onDeleteHandler: (todoId: string) => void;
  onDoneHandler: (todo: Todo) => void;
  onDetailHandler: (todo: Todo) => void;
}> = (props) => {
  return (
    <Div>
      <TodoDiv>
        <p>To Do</p>
        {props.todoItems.map((item) => (
          <TodoItem
            key={item.id}
            item={item}
            onRemoveItem={props.onDeleteHandler.bind(null, item.id)}
            onDoneItem={props.onDoneHandler.bind(null, item)}
            onDetailItem={props.onDetailHandler.bind(null, item)}
          />
        ))}
      </TodoDiv>
      <hr />
      <DoneDiv>
        <p>Done</p>

        {props.doneItems.map((item) => (
          <TodoItem
            key={item.id}
            item={item}
            onRemoveItem={props.onDeleteHandler.bind(null, item.id)}
            onDoneItem={props.onDoneHandler.bind(null, item)}
            onDetailItem={props.onDetailHandler.bind(null, item)}
          />
        ))}
      </DoneDiv>
    </Div>
  );
};

export default Todos;
const Div = styled.div`
  display: flex;
`;
const TodoDiv = styled.div`
  width: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 74vh;
  overflow: auto;
`;
const DoneDiv = styled.div`
  width: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 74vh;
  overflow: auto;
`;

 

TodoItems.tsx

import React from 'react';
import styled from 'styled-components';
import Todo from '../models/todo';

const TodoItem: React.FC<{
  item: Todo;
  onRemoveItem: (event: React.MouseEvent) => void;
  onDoneItem: (event: React.MouseEvent) => void;
  onDetailItem: (event: React.MouseEvent) => void;
}> = (props) => {
  return (
    <Div>
      <TextDiv onClick={props.onDetailItem}>{props.item.text}</TextDiv>
      <p>
        <button onClick={props.onDoneItem}>
          {props.item.isDone ? '취소' : '완료'}
        </button>
        <button onClick={props.onRemoveItem}>삭제</button>
      </p>
    </Div>
  );
};
export default TodoItem;

const Div = styled.div`
  border: 1px solid black;
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 50%;
  height: 7.5rem;
  margin: 20px;
`;

const TextDiv = styled.div`
  width: 100%;
  height: 4rem;
  display: flex;
  align-items: center;
  justify-content: center;
`;

 

DetailPage.tsx

import styled from 'styled-components';
import Todo from '../models/todo';

const DetailPage: React.FC<{ todo: Todo }> = (props) => {
  return (
    <DetailDiv>
      <div>{props.todo.id}</div>
      <div>{props.todo.text}</div>
      <div>
        <button>수정</button>
        <button>삭제</button>
      </div>
    </DetailDiv>
  );
};

export default DetailPage;

const DetailDiv = styled.div`
  width: 60vw;
  height: 80vh;
  margin: 5rem auto;
  background-color: gray;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;
반응형

댓글