[Assignment 8 회고록] 모두 컴퍼니 - TS 기반 투두리스트와 animation

이다은·2021년 8월 29일
0

프리온보딩-회고록

목록 보기
9/11
post-thumbnail

🔗 Github

🔗 배포 링크

🔗 피그마 링크


  • 이번 과제는 4명의 팀원이 각자 브랜치 생성 ➡ PR 생성 ➡ 코드리뷰 ➡ 수정 ➡ 머지의 과정을 거쳐 협업하여 진행했습니다.😊

  • 과제를 마치고, 총 16개의 조 중에 제일 우수한 프로젝트로 평가 받아서 기분이 매우 좋습니다.🎉

  • 코드 리뷰 또한 좋은 예시로 선정되었습니다.🎉


과제를 끝내고 회고 내용

🔰 Git > 코드 리뷰와 브랜치 관리

  • 프리온보딩 과정을 하면서 Live Share를 통한 협업을 여러번 진행했는데, 이번 과제는 Live Share 기능을 사용하지 않고, 각자 분담된 기능을 브랜치로 만들고 PR을 올리면 모든 팀원이 코드 리뷰 하는 방식으로 진행되었다.

  • 한 달 넘게 같이한 팀원들과 익숙해졌는데, 새로 조가 바뀌면서 상혁님, 이슬님, 준희님과 git 컨벤션이라던지, 이슈 관리, 브랜치 및 PR 관리 등 어떻게 해야할지 처음부터 다시 정하는 시간을 가졌다.

  • 코드 리뷰!
    ➡ Git에 팀원분들이 PR을 올리면, 꼼꼼하게 코드를 보려고 노력했다. 이해가 안되면 서로 설명을 해주고 피드백을 주고 받았다. 코드 리뷰가 너무 좋았던게 내가 미쳐 생각하지 못했던 부분을 말씀해주셔서 생각해보고 더 좋은 방향으로 리팩토링을 진행할 수 있었다. (물론 구현해야 할 남은 기능도 있는데, 현재 기능을 리팩토링 한다고 밤을 새웠지만...그래도 팀원분들이 더 쉽게 이해하고 사용하시는 걸 보니 뿌듯했다!)

🔰 애니메이션 기능(칸반보드 형태의 Drag and Drop)

  • Drag and Drop 기능은 처음 사용해봤다. 사실 이벤트 리스너를 통해서 기능을 구현할 수 있다는 것 조차 몰랐었다..

  • 기능을 구현하기 전, 구글링을 통해 예시 코드를 참고하고 상하로 리스트를 움직이는 법에 대해서 공부했다.
    그러나! 우리의 투두리스트는 칸반보드 형태로 상태에 따라 좌우로도 움직여야 했다. 기존 코드에서 보다 더 복잡해졌지만 다들 해본 적 없는 기능에 도전정신을 가지며 즐겁게 과제를 진행하였다.

칸반보드형태

🔰 전역상태에서 관리하는 모달창

  • 위에서 말한, 코드리뷰를 통해 리팩토링한 기능이다. Portal을 사용해서 모달창 기능을 구현했고, 투두리스트 수정 버튼을 클릭하면 모달창이 생성되는 구조라서, 처음에는 수정 버튼이 있는 곳에 모달 관련 컴포넌트를 넣었다.

  • 그런데 투두리스트를 생성할 때, 입력 validation을 만족하지 못하면 모달창을 재사용 했으면 좋겠다는 의견을 받았다.

  • Context API를 활용하여 dispatch(nodal("띄울 모달명"))으로 띄울 모달명을 셋팅하면, App.tsx 에서 모달명에 맞는 화면을 보여줄 수 있도록 변경했다.
    리팩토링 한 결과 팀원분들이 머지 후 쉽게 모달창을 띄울 수 있게 되었다!

🔰 사용자를 위한 디자인 설계

  • 과제를 처음 받고 내용을 읽어보니, 글로만 읽고 구현하면 서로 다르게 이해한 부분이 생길 수 있겠다 하는 생각이 들었다. 또 새로운 조편성으로 팀원들이 바뀌다보니 말로만 의견을 전달하면 서로 잘못 이해하는 기능이 생기지 않을까 염려되었다.

  • 그래서 모두가 피그마에 접속해, 우리가 구현하고자 하는 칸반보드 형태의 투두리스트를 만들어 보았다. 사실 피그마를 전문적으로 사용하지는 못하는데, 팀원분 중에 디자이너로 일하셨던 분이 계셔서 많은 도움을 받았다.

  • PR을 올리고 머지하면서 느낀점은, 모든 팀원분들이 정말 피그마와 동일하게 만들어주셨다!😊

  • 과제를 제출하기 전, 4명이서 모두 Live Share로 같은 코드를 보면서 수정해야할 부분에 대해 의논하고 리팩토링을 진행하였는데, 특히 디자인 관련해서 "사용성을 위해 svg 버튼에 패딩주기 또는 색상 선정을 위한 팁" 등 디테일 한 부분까지 말씀해주셔서 많은 도움이 되었다.


과제 내용

[공통]

  • TypeScript 사용
  • 데이터는 로컬의 dummy data 로 자유롭게 구성할 것 (format: json)
  • UI 라이브러리 사용하지 않을 것을 권장

[상세기능]

  • 스크롤 시 Header 고정
  • 투두리스트에 적합한 타입 구성
  • Task 목록 조회, 새로운 Task 추가, Task 삭제, Task 수정
  • Task 상태 변경(시작전,진행중,완료)
  • Task 필터링(상태, 생성일, 생성자, 중요도) : 최소 두가지 이상 조건으로 필터링
  • 투두리스트에 적절한 애니메이션을 추가
    ➡ Drag & Drop 애니메이션 구현

구현 내용

🔰 Drag & Drop 기능

➡ 기존에 진행했던 투두리스트와 다른 점인 애니메이션(Drag & Drop) 부분 구현한 내용을 작성

📌 1. Drag & Drop 기능을 커스텀 훅으로 생성해서 관리하고자 했다.

  • handleDragStart : 사용자가 요소를 끌어 시작할 때 발생
  • handleDragEnter : 드롭 타겟 들어가면 이벤트가 발생
  • handleDragOver : 드롭 타겟 위에 드래그되는 경우에 이벤트가 발생
  • handleDragLeave : 드래그 요소가 드롭 대상을 떠날 때 발생
// utils/hooks/useTodoItemDnD.ts 파일 생성

import { useState } from 'react';

export const useTodoItemDnD = (id: number) => {
  const [isDragging, setisDragging] = useState(false);
  const [isDragOver, setIsDragOver] = useState(false);

  const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
    setisDragging(true);
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', `${id}`);
  };

  const handleDragEnter = () => {
    setIsDragOver(true);
  };

  const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    setIsDragOver(true);
  };

  const handleDragLeave = () => {
    setIsDragOver(false);
  };

  return {
    isDragging,
    isDragOver,
    handleDragStart,
    handleDragEnter,
    handleDragOver,
    handleDragLeave,
    setIsDragOver,
  };
};

📌 2. TodoItem 컴포넌트를 감싸는 최상단에 useTodoItemDnD 관련 이벤트 리스너를 등록한다.

// TodoItem.ts

import { useTodoItemDnD } from 'utils/hooks';
...

const {
    isDragOver,
    handleDragStart,
    handleDragOver,
    handleDragEnter,
    handleDragLeave,
    setIsDragOver,
} = useTodoItemDnD(todo.id);

  ...

  
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
    setIsDragOver(false);
    const movingTarget = e.dataTransfer.getData('text/plain');
    dispatch(swap({first: +movingTarget, second: todo.id})); // ✅
};
  ...
  
<ItemContainer
        draggable
        isDragOver={isDragOver}
        onDragStart={handleDragStart}
        onDragOver={handleDragOver}
        onDragEnter={handleDragEnter}
        onDragLeave={handleDragLeave}
        onDrop={handleDrop}
>
       ...
</ItemContainer>

📌 3. reducer에서 dispatch(swap()) 액션이 실행될 경우, Todo의 상태에 따라 순서를 변경하거나 state를 변경한다.

// context/TodoContext/reducer.ts

export default function reducer(state: IState, action: Action): IState {
  const { type, payload } = action;
  switch (type) {
    ...
    case SWAP:
      return { ...state, todos: swapTodos(state.todos, payload) }; // ✔
	...
    default:
      return state;
  }
}

...

const swapTodos = (prevTodos: ITodos, payload: ISwap): ITodos => { // ✔
  const { first: firstId, second: secondId } = payload;
  if (firstId === secondId) return prevTodos;

  const firstTodo = prevTodos.find((todo) => todo.id === firstId);
  const secondTodo = prevTodos.find((todo) => todo.id === secondId);
  if (!firstTodo || !secondTodo) return prevTodos;

  if (firstTodo.status === secondTodo.status) { 
    // ✅ Todo의 상태가 동일하여 상하로 움직인 경우
    const firstIndex = prevTodos.findIndex((todo) => todo.id === firstId);
    const secondIndex = prevTodos.findIndex((todo) => todo.id === secondId);
    if (firstIndex === -1 || secondIndex === -1) return prevTodos;
    const newTodos = [...prevTodos];
    [newTodos[firstIndex], newTodos[secondIndex]] = 
      [newTodos[secondIndex], newTodos[firstIndex]];
    return newTodos;
  } 
  else {
    // ✅ Todo의 상태가 달라서, Todos의 상태도 업데이트 해야하는 경우.
    const newTodos = [...prevTodos];
    const firstIndex = prevTodos.findIndex((todo) => todo.id === firstId);
    const firstTodo = newTodos.splice(firstIndex, 1)[0];
    const secondIndex = newTodos.findIndex((todo) => todo.id === secondId);
    firstTodo.status = newTodos[secondIndex].status;
    newTodos.splice(secondIndex, 0, firstTodo);
    return newTodos;
  }
};

📌 4. TodoBoxTodoItem을 Drag&Drop 할 수도 있다. TodoBoxuseTodoItemDnD 관련 이벤트 리스너를 등록한다.

// TodoBox.ts

import { useTodoItemDnD } from 'utils/hooks';
...

 const { 
     isDragOver, 
     setIsDragOver, 
     handleDragStart, 
     handleDragOver, 
     handleDragLeave 
 } = useTodoBoxDnD(ref);

...

  
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
  if (!ref || !ref.current) return;
  if (!ref.current.isSameNode(e.target as Node)) return;
  const id = +e.dataTransfer.getData('text/plain');
  console.log('TodoBox', id, status);
  dispatch(update({ id, status })); // ✅ update 액션을 통해 state를 변경한다.
  setIsDragOver(false);
};

...
  
<TodoSectionWrapper
      ref={ref}
      draggable
      isDragOver={isDragOver}
      onDragStart={handleDragStart}
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
>
       ...
</TodoSectionWrapper>
profile
단단_프로트엔드개발자!

0개의 댓글