웹 풀 사이클 데브코스

[TIL] Day52 - Drag And Drop, 구글 Login, Firebase 배포

닿다라다나닷 2024. 6. 29. 23:30

Drag And Drop : DND 앱

CRA > `react-beautiful-dnd`

드래그앤드랍을 위한 라이브러리

 

GitHub - atlassian/react-beautiful-dnd: Beautiful and accessible drag and drop for lists with React

Beautiful and accessible drag and drop for lists with React - atlassian/react-beautiful-dnd

github.com

  • <DragDropContext /> : 사용하고 싶은 부분 전체를 감싸는 wrap
  • <Droppable /> : 드래그 떨굴 수 있는 부분
  • <Draggable /> : 드래그 할 아이템

+ provied + {...provided.droppableProps} + ref={provide.innerRef} > 스타일 및 조회를 위한 속성!

import "./App.css";
import { useState } from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";

const finalSpaceCharacters = [
  {
    id: "gary",
    name: "Garry Goodspeed",
  },
  {
    id: "cato",
    name: "Little Cato",
  },
  {
    id: "kvn",
    name: "KVN",
  },
];

function App() {
  const [characters, setCharacters] = useState(finalSpaceCharacters);

  return (
    <div className="App">
      <header className="App-header">
        <h1>Final Space Characters</h1>
        <DragDropContext onDragEnd={handleEnd}>
          <Droppable droppableId="characters">
            {(provided) => (
              <ul
                className="characters"
                {...provided.droppableProps}
                ref={provided.innerRef}
              >
                {characters.map(({ id, name }, index) => {
                  return (
                    <Draggable key={id} droppableId={id} index={index}>
                      {(provided) => (
                        <li
                          ref={provided.innerRef}
                          {...provided.draggableProps}
                          {...provided.dragHandleProps}
                        >
                          <p>{name}</p>
                        </li>
                      )}
                    </Draggable>
                  );
                })}
                {provided.placeholder}
              </ul>
            )}
          </Droppable>
        </DragDropContext>
      </header>
    </div>
  );
}

export default App;

사용자가 요소를 드래그 하는 경우 className 속성을 selected로 변경하고, 나중에 스타일을 적용하는데 사용한다.

placeholder 속성은 목록에 빈 공간을 만들어 드래그 작업이 저연스럽게 느껴지게 된다.

 

result 매개변수에는 souce 항목 및 대상 위치와 같은 드래그 이벤트에 대한 정보가 포함된다.

*리액트에서 state는 불변성을 지켜줘야 한다 > 메소드 Array.from

const handleEnd = (result) => {
    if (!result.destination) return;
    const items = Array.from(characters);
    const [reorderedItem] = items.splice(result.source.index, 1);
    items.splice(result.destination.index, 0, reorderedItem);
    setCharacters(items);
  };
  //...
  <DragDropContext onDragEnd={handleEnd}>
  1. 예외로 목적지가 없으면(드래그가 영역 밖에 되는 경우) 이 함수를 종료한다.
  2. 불변성을 지키는 새로운 데이터 items 생성
  3. 변경시키는 아이탬을 splice로 배열에서 지우고, 그 값을 reorderedItem 에 저장한다
  4. 원하는 자리에 reforderedItems 를 insert 해준다. 

 

**이때 react 18의 StrictMode에서는 이전 버전들과 다르게 디버깅 목적을 위해 라이프사이클을 의도적으로 두 번 실행시키는데, 그 부분에서 등록된 Droppable의 ref 자체가 사라지는 이슈 ===  DroppableId를 찾지 못하는 에러가 발생했다.

간단하게 StrictMode 를 지워도 되지만(얘는 전에도 자꾸 두번호출해서 홧김에 지워버렸었는데ㅋㅋ) 개발 과정에서 버그를 찾을 수 있도록 도와줘서 제거하는게 좋은 선택지는 아니라고 멘토님께서 말씀해주셨다... 반성...

 

requestAnimationFrame을 사용해 컴포넌트가 렌더링 된 후 에 컴포넌트르 등록했다 > 1차 렌더링 후 paint 이후에 등록하는 방식이다.

브라우저에서 화면의 새로 고침 주기에 맞춰 콜백 함수를 호출하는 메서드이다. 컴포넌트가 첫 번째 렌더링을 완료한 후에 설정이 이루어지게 한다.

> Droppable 컴포넌트를 한번 렌더링 된 후에 렌더링 되도록 컴포넌트를 조건부 렌더링으로 감싼다.

///..
<DragDropContext onDragEnd={handleEnd}>
  {isDroppableReady && (
    <Droppable droppableId="characters">
      {(provided) => (
        <ul
        //...

 

DND 플젝에 적용하기!

requestAnimationFrame을 사용해야 하는줄 알고 고민 많이 했는데,, 드래그 컴포넌트 나눠서 props로 값 전달해주는 방식은 에러가 발생하지 않네..?

app.tsx 에서 함수 선언

const handleDragEnd = (result: any) => {
    const { destination, source, draggableId } = result;

    const sourceList = lists.filter(
      (list) => list.listId === source.droppableId
    )[0];

    dispatch(
      sort({
        boardIndex: boards.findIndex(
          (board) => board.boardId === activeBoardId
        ),
        droppableIdStart: source.droppableId,
        droppableIdEnd: destination.droppableId,
        droppableIndexStart: source.index,
        droppableIndexEnd: destination.index,
        draggableId: draggableId,
      })
    );

 

boardsSlice.ts 에서 인덱스 옮겨지는 기능 생성

sort: (state, { payload }: PayloadAction<TSortAction>) => {
      //same list
      if (payload.droppableIdStart === payload.droppableIdEnd) {
        const list = state.boardArray[payload.boardIndex].lists.find(
          (list) => list.listId === payload.droppableIdStart
        );
        //변경시키는 아이템을 배열에서 지워주고 + 리턴값으로 지워진 아이템 가져와서 + 넣어준다.
        const card = list?.tasks.splice(payload.droppableIndexStart, 1);
        list?.tasks.splice(payload.droppableIndexEnd, 0, ...card!);
      }

      //other list
      if (payload.droppableIdStart !== payload.droppableIdEnd) {
        const listStart = state.boardArray[payload.boardIndex].lists.find(
          (list) => list.listId === payload.droppableIdStart
        );
        const card = listStart!.tasks.splice(payload.droppableIndexStart, 1);
        const listEnd = state.boardArray[payload.boardIndex].lists.find(
          (list) => list.listId === payload.droppableIdEnd
        );
        listEnd?.tasks.splice(payload.droppableIndexEnd, 0, ...card);
      }
    },

 

로그인 기능 > Firebase 구글 로그인

Firebase 구글 서비스 제공

Go to console > 프로젝트 추가 > 애널리스트 등록

 

로그인 하면 > Redux Store에 유저 데이터 넣기

 

firebase-tools 배포

`npm i -g firebase-tools`

`firebase login` > Success! Logged in as cdh010126r@gmail.com

리액트 src > build 파일 `npm run build` > dist 폴더를 이용해 배포

`firebase init`

 

https://dev-trello.web.app/

 

Vite + React + TS

 

dev-trello.web.app