개발새발 로그

react-beautiful-dnd 라이브러리를 사용하면서.. 본문

React

react-beautiful-dnd 라이브러리를 사용하면서..

이즈흐 2024. 7. 31. 23:40

✨DragDropContext 

드래그 앤 드롭을 가능하게 허용할 부분을 감싸주는 태그

  • onDragEnd : 드래그를 끝낸 시점에 호출되는 함수 (필수 속성)
  • onDragStart : 드래그가 시작할 때 호출
  • onDragUpdate: 드래그가 시작할 때 & 요소 간의 순서가 바뀔 때 호출

Droppable

드롭이 가능한 부분을 감싸주는 태그

  • droppableId : 구분자 (필수 속성)
  • children은 반드시 함수여야 함 (JSX 태그X) -> 따라서 함수로 JSX 태그를 리턴하는 형식을 자식에 넣어주어야 한다
  • Droppable이 내부 함수에 전달해주는 인자 : provided
    • 내부 JSX 태그에 ref={provided.innerRef} {...provided.droppableProps} 속성을 부여해줘야 한다
  <DragDropContext>
      <Droppable droppableId="cardlists">
        {provided => (
          <div className="cardlists" {...provided.droppableProps} ref={provided.innerRef}>
           
          </div>
        )}
      </Droppable>
    </DragDropContext>

Draggable

 드래그가 가능한 부분을 감싸주는 태그

  • draggableId : 구분자 (필수 속성)
  • index : 정렬을 위한 데이터 (number)
  • children은 반드시 함수여야 함 (JSX 태그X) -> 따라서 함수로 JSX 태그를 리턴하는 형식을 자식에 넣어주어야 한다.
  • Draggable이 내부 함수에 전달해주는 인자 : provided
    • provided.draggableProps : 태그의 모든 영역에서 드래그 가능하도록 설정
    • provided.dragHandleProps : 특정 영역에서만 드래그 가능하도록 설정

🚨Draggable 의 draggableId와 key가 같아야 한다.

<Draggable draggableId<={`test-${e.id}`} index={i} key={`test-${e.id}`}>
 return (
    <DragDropContext onDragEnd={handleChange}>
      <Droppable droppableId="cardlists">
        {provided => (
          <div className="cardlists" {...provided.droppableProps} ref={provided.innerRef}>
            {post.map((e: any, i: number) => (
              <Draggable draggableId={`test-${e.id}`} index={i} key={`test-${e.id}`}>
                {(provided, snapshot) => {
                  return (
                    <div
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      ref={provided.innerRef}
                    >
                      // 원하는 컴포넌트 넣어주기
                    </div>
                  );
                }}
              </Draggable>
            ))}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );

 

provided.placeholder 추가

컴포넌트 이동시 자리 배열이 알맞게 위치하도록 placeholder 함수가 필요하다.
만약 넣지 않으면, placeholder가 없다는 에러 메세지가 뜬다.

 

 


🤔react-beautiful 타입을 찾을 때

https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/types.md

 

react-beautiful-dnd/docs/guides/types.md at master · atlassian/react-beautiful-dnd

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

github.com

🤔strictMode를 사용한다면

드래그가 안되고 이런 오류가 뜬다.

strictMode를 없애주면 해결된다!

 

 

https://codesandbox.io/s/react-beautiful-dnd-examples-multi-drag-table-with-antd-gptbl?file=/src/multi-table-drag/index.js:2263-2618

 

react-beautiful-dnd examples - multi drag table with antd - CodeSandbox

react-beautiful-dnd examples - multi drag table with antd by nnt25251325 using antd, react, react-beautiful-dnd, react-dom, react-scripts

codesandbox.io

 

 

 

 

 

 

 


🤔멀티드래그?

외부 클릭시 멀티 드래그 초기화하는 방법

1. useRef를 사용해서 각 Droppable에 등록한 후 해당 ref가 아니면 초기화 시키는 방법

- useRef를 하나 생성해야함

- ref를 props로 내려야함

 

2. window에 이벤트를 등록하고, 멀티 드래그로 선택된 데이터 초기화하는 방법

- window에만 이벤트를 등록하고 멀티 셀렉트 이벤트에서는 stopPropagation() 사용

 

3. 멀티 셀렉트된 데이터를 저장하는 상태의 자료구조가 Set인 이유?

- Set은 해시 테이블 기반의 구조로 구현되어 있어, 값의 존재 여부를 O(1) 시간 복잡도로 빠르게 확인할 수 있습니다.

- Set은 유용한 메서드(add(), delete(), has(), clear(), forEach())를 제공하여 다양한 작업을 간편하게 수행할 수 있습니다.

 

includes():

  • 배열은 요소를 순차적으로 검색하므로, 최악의 경우 O(n) 시간 복잡도를 가집니다. 즉, 배열의 길이에 따라 성능이 저하될 수 있습니다.

has():

  • Set은 해시 테이블 기반으로 구현되어 있어, 평균적으로 O(1) 시간 복잡도로 값을 검색할 수 있습니다. 따라서 대량의 데이터에서 값의 존재 여부를 확인할 때 성능이 더 뛰어납니다.

많은 조건들

Draggable은 isDragDisabled라는 값을 받아서 드래그가 안되도록 할 수 있는데 나는 활용하지 않았다.

1. 짝수가 나열되면 안된다.

문제 상황: 멀티 드래그시에도 짝수만을 선택했을 때 경고가 나와야 함

해결 방안: 셀렉된 아이템들을 드래그 할 때 검사해서 경고 메세지를 상태에 저장 후 출력합니다.

 

문제 상황: 드래그 중일 때 어떤 컬럼에 도착한다면 미리 짝수 나열이 되는지 확인하고 경고가 나와야 함

해결 방안: onDragUpdate 때에 시작 지점 컬럼과 도착 지점 컬럼에 짝수가 나열되는지 확인합니다.

+ 같은 컬럼 내의 이동일 때는 셀렉된 아이템이 이동된 이후의 수열을 미리 검사해서 결과를 알려줍니다.

+ 다른 컬럼 내의 이동일 때는 셀렉된 아이템이 도착지점에 이동된 이후를 미리 검사합니다.

즉 onDragUpdate때 미리 짝수가 나열되는지 예상해서 검사 후 화면에 경고를 나타내고,

onDragEnd에는 짝수 검사가 통과된 것이므로 업데이트만 수행합니다.

 

문제 상황 : 시작 지점에서 드래그할 요소가 빠질 때 해당 컬럼에 짝수 나열이 생긴다면 경고가 나와야 함

해결 방안 : onDragUpdate때 같은 컬럼 내의 이동과 다른 컬럼 내의 이동을 분리해서 짝수가 나열 되는지 미리 확인합니다. 위랑 같은 내용이 아닌가?

2. 멀티 드래그

문제 상황: 멀티 드래그할 때 어떤 기준으로 정렬이 되어야할까? -> 정렬의 기준이 잡혀야 짝수 나열 조건에서 유효한지 예상이 가능함

해결 방안: 멀티 드래그할 때는 무조건 정렬이 되도록 강제했다.

 

 

문제 상황: 멀티 드래그 선택 후에 ctrl 버튼을 떼고 클릭 시에는 멀티 드래그를 취소하고 단일 드래그를 해야하는가?

해결 방안: 아래 코드에서 보이는 것처럼 만약 onDragStart 시 자신이 셀렉한 아이템이 아니라면 셀렉아이템을 초기화 한다.

if (!selectedItems.has(Number(draggableId))) setSelectedItems(new Set())

 

문제 상황: 1 컬럼요소와 2컬럼요소를 멀티 셀렉 후 4번으로 드래그가 가능한 것인가?

해결 방안: 한 컬럼 내에서만 멀티셀렉이 가능하도록 강제했다.

3. 복잡한 멀티 드래그와 단일 드래그 조건

문제

1. 활용할 수 있는 값들의 한정됨

  - destinationIndex: 현재 드래그할 위치의 값

     -> 단일 드래그 기준이라서 만약 멀티 드래그일 때는 그저 하나의 값을 제외한 이후의 인덱스를 반환해주는 상황

     -> 예를 들어 1을 2 뒤로 드래그하면 destinationIndex는 1이지만, 1,2,3을 4 뒤로 드래그하면 destinationIndex는 3

  - selectedItems : 현재 선택된 값들의 배열(각 데이터의 id값이 들어가 있음)

     -> 단일 드래그일 때와 멀티 드래그일 때를 맞추기 위해 배열로 만듦

     -> 아이디가 1,2,3,4...로 인덱스 순처럼 되어있지만 재활용가능성을 위해 인덱스 처럼 활용하지 않음

  - finishItems : 선택된 데이터들이 제외된 배열

     -> 위에서 말했듯이 destinationIndex는 드래그한 요소를 제외한 이후의 배열을 기준으로 도착지점 인덱스를 정함

     -> 그러므로 선택된 데이터를 제거한 이후의 배열이 필요한 것임

   - sourceIndex : 드래그를 시작하는 인덱스 지점

     -> 위에서 말한 단일드래그에서의 destinationIndex와 멀티드래그에서의 destinationIndex가 일관되지 않고있음

     -> 이를 위해서 조건 처리가 필요한데

     -> 1. 현재 선택된 데이터가 다수인가? 를 확인

     -> 2. 현재 드래그 시작 인덱스가 destinationIndex보다 작은가? 를 확인

     -> 3. 위 두 조건이 true이면 "아래로 드래그하는 중" 이고, "멀티 드래그" 중임을 확인

     -> 4. 위 두 조건이 false이면 "위 시나리오를 제외한 모든 상황" 임을 확인

  => 이를 통해서 멀티 드래그와 단일 드래그의 일관성을 유지

 

이게 다시 설명하면

1. 단일 드래그이고, 아래로 드래그 할 때는 "destinationIndex" 값이 필요

2. 단일 드래그이고, 위로 드래그 할 때는 " destinationIndex " 값이 필요

3. 멀티 드래그이고, 아래로 드래그 할 때는 "destinationIndex - (선택된 데이터 갯수 - 1)"이 필요

4. 멀티 드래그이고, 위로 드래그 할 때는 "destinationIndex" 값이 필요

이게 다 destinationIndex"드래그 하고 있는 데이터를 제외한 이후의 도착 지점 인덱스" 이기 때문임

 

 

 

 

UX를 위한 고민

 

1. 드래그한 요소들을 로컬스토리지에 저장

로컬 스토리지에 저장하는 건 사실 좋지 않다.

용량 제한: localStorage는 일반적으로 5MB의 저장 용량 제한이 있습니다. 큰 데이터 객체를 저장하면 이 용량을 초과할 수 있습니다.

성능 저하: 큰 객체를 JSON 문자열로 변환하거나 변환된 문자열을 읽고 쓰는 과정은 시간이 걸릴 수 있으며, 이는 애플리케이션의 성능에 영향을 미칠 수 있습니다.

데이터 무결성: 복잡한 객체를 로컬 스토리지에 저장할 때, 객체 구조가 변경되면 기존의 데이터가 깨질 수 있습니다. 이를 관리하기가 어려워질 수 있습니다.

동기적 작업: localStorage의 모든 작업은 동기적으로 수행되기 때문에, 대량의 데이터를 처리할 경우 UI가 멈추는 것 같은 느낌을 줄 수 있습니다.

 

해결 방법

1. 데이터 분할 저장

const saveColumnsToLocalStorage = (columns) => {
  columns.forEach(column => {
    localStorage.setItem(`column-${column.id}`, JSON.stringify(column));
  });
}

2.만료날짜를 설정해서 불필요한 저장을 못하게끔 해야한다.

// 현재 시간에 7일을 더한 만료 일자를 계산합니다.
const expirationDate = new Date().getTime() + (7 * 24 * 60 * 60 * 1000);

// 데이터와 함께 만료 일자를 LocalStorage에 저장합니다.
localStorage.setItem('myData', JSON.stringify({ data: 'hello world', expiration: expirationDate }));

//--------------------------------------------

// LocalStorage에서 데이터를 가져옵니다.
const myData = JSON.parse(localStorage.getItem('myData'));

// 만료일이 지났으면 데이터를 삭제합니다.
if (myData && myData.expiration && new Date().getTime() > myData.expiration) {
  localStorage.removeItem('myData');
}
출처: https://autolist.tistory.com/entry/자바스크립트-LocalStorage-만료-시간-설정 [세상을 바꾸는 개발자:티스토리]

 

 

 

2. 모바일 환경 진동 트리거

https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/sensors/touch.md

 

react-beautiful-dnd/docs/sensors/touch.md at master · atlassian/react-beautiful-dnd

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

github.com

 

 

3. 되돌리기 기능

column이 업데이트 될 때마다 이전 column을 상태에 저장한다.

초기화 시에도 이전 column을 저장한다.

되돌리기는 최대 5개의 이전 결과를 저장가능하게 한다.

 

 

4. 초기화 기능

초기화 클릭시 애플리케이션 시작 값으로 되돌린다.

초기화 시에도 되돌리기는 가능하다.

초기화된 결과가 로컬스토리지에 저장되도록 한다.

 

728x90
반응형
LIST