react-beautiful-dnd

Kyeongeun Jo·2022년 7월 20일
0

drag and drop 을 활용하여 sort 기능을 제공해주는 라이브러리

오늘은 react에서 drag and drop을 쉽게 활용할 수 있는 라이브러리를 소개해보려고 한다.

사용 계기

사실 react-beautiful-dnd를 사용하기 전엔 react-dnd를 사용했으나,

  • multiple lists를 위한 기능으로 사용하자니 doc 설명들이 이해하기 어려운 부분
  • 확실히 늘어나는 코드량
  • react version 이 맞지 않아 처음에 install 할 수 없었던 이유

가 되었다.
버전 오류로 인한 해결은 아래 --legacy-peer-deps 라는 라이브러리를 같이 설치하면 해결이 된다.

--legacy-peer-deps : version dependency를 무시해 충돌을 막아줌

(^18.2.0) 버전.

npm install --save react-beautiful-dnd --legacy-peer-deps

사용 목적

multiple lists (🤔..!?) 와같이 리스트 하위에도 기능하는 또다른 리스트를 구현하기 위함.

아래처럼 [ 임시의 정원, 황제, 대한제국, 통치 ] 와 같은 부모 List 가 있으며,
하위로 auidoList가 뿌려지게 되고, 이 오디오 리스트 또한 dnd 기능이 필요했다.

비교적 react-beautiful-dnd 라이브러리는 사용성이 간편한 편이라 큰 어려움은 없었다.

필요 태그

DragDropContext

드래그 할 요소들을 감싸고 있는 wrapper 개념으로 drag 할 요소들의 가장 상위에서 사용해야 한다.
리스트 안에 리스트를 만들어야하기 때문에, 2개의 태그가 필요했다.

<DragDropContext>
	...
    <DragDrapContext>
    	...
    </DragDrapContext>
    ...
</DragDropContext>

Droppable

drop을 할 수 있는 영역을 감싸고 있다.
Droppable 함수 바로 하위로 오는 친구에게는
droppable로 사용할 컴포넌트에 적용이 되야하는 프로퍼티와
컴포넌트들이 서로 상호작용을 도와주는 ref 콜백의 역할을 적용시켜 주어야 한다.

ref={provided.innerRef}
{...provided.droppableProps}

<DragDropContext>
	<Droppable>	
    	<div
        ref={provided.innerRef}
        {...provided.droppableProps}
			>
    	// 중제목 리스트
    
    	<DragDrapContext>
        	<Droppable>
            	// 중제목에 포함된 오디오 리스트
    			...
        	</Droppable>
    	</DragDrapContext>
        
	</Droppable>
</DragDropContext>

Draggable

drag를 할 수 있는 주체적인 요소이며, Draggable에는 세가지 요소가 필요하다.

  • index
  • key
  • draggableId
    이며, key가 필요한 이유는 상단에서 배열들을 map 함수를 통해 뿌려주기 때문 (for문)
    이 때 !!! 가장 중요한 포인트는 key와 draggableId를 동일하게 주어야 한다는 점이다.
    다르게 할 경우, 각자가 어떤 객체인지 인식을 못하는 것 처럼 작동이 잘 되지 않는다.

또한 drappable 과 동일하게 하위로 떨어지는 태그에게도 역시
draggableProps 이라는 프로퍼티를 부여해주어야 하며,
dragHandleProps 이라는 실제로 drag가 어느 시점부터 handling 되는지,
적절한 컴포넌트에 부여해주면 된다.
(예를들어 왼쪽 = ham 모양의 버튼으로만 컨트롤 하고싶다면, 그 컴포넌트에 부여해줄 수 있다.)

ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}

<Draggable 
	index={index} 
    key={"subTopicList" + index}  
    draggableId={"subTopicList" + index} 
>	
	{provided => (
    	<div 
        	
            ref={provided.innerRef} 
            {...provided.draggableProps} 
            {...provided.dragHandleProps}
		>
            ...
		</div>
</Draggable>
            

onDragEnd

drag를 마칠 때 일어나는 이벤트이다. DragDropContext 요소에 포함시켜주어야한다.
따로 함수를 빼서 다른곳에 다양하게 적용을 해두었다.
리스트들의 index 값들의 배치를 재 정렬해주는 함수이다.

export const setSequence = (list, startIndex, endIndex) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0 , removed);

    return result;
}

최종 코드

Audio.jsx
subTopicList 및 상태들을 자식 components에게 넘겨줌

<ul className={styles.subList}>
  { data 
      ? <SubTopicListDnd
          subTopicList={subTopicList}
          subTopicListDt={subTopicListDt}
          setSubTopicList={setSubTopicList}
          />
      : <p 
      		className={"noData"} 
      		style={{ paddingBottom: "3rem" }}
        >
        	카테고리 / 전시관을 선택해주세요.
       </p>
  }
</ul>
SubTopicListDnd.jsx
가장 보여지는 중제목의 List

<DragDropContext onDragEnd={onDragEnd} >
	<Droppable droppableId="subTopicList">
    	{(provided, snapshot) => (
        	<div
                ref={provided.innerRef}
                {...provided.droppableProps}
			>
            // 하위 List 생성
            	{subTopicList.map((data, index) => {
                	return (
         	           <Draggable 
                       		index={index} 
                            draggableId={"subTopicList" + index} 
                       	>	
                       		{provided => (
                            	<div 
                                	key={data.id} 
                                    ref={provided.innerRef}
                                    {...provided.draggableProps} 
                                    {...provided.dragHandleProps}
                                >
                                	<SubTopicDetailDnd
                                    	data={data}
                                        index={index}
                                        subTopicList={subTopicList}
                                        setSubTopicList={setSubTopicList}
                                     />
                                </div>
                             )}
                             </Draggable>
                            )
                        })}
                        {provided.placeholder}
                    </div>
                )}
            </Droppable>
        </DragDropContext>
SubTopicDetailDnd.jsx

<DragDropContext onDragEnd={onDragEnd}>
	<Droppable droppableId={"audioList"}>
    	{provided => (
        	<div 
            	className={styles.listBox} 
                ref={provided.innerRef} 
                {...provided.droppableProps} 
                {...provided.dragHandleProps}
            >
            	<li>
                	<div>
                    	<h3 
                        	onClick={() => handleHide(index)}
                        >
                         {data.title} <KeyboardArrowDown/>
                       	</h3>
                    </div>
                    <div 
                    	className={styles.contentRef} 
                        ref={el => contentRef.current[index] = el}
                    >
                    	{audioList.length === 1 ? (
                            	... // dnd 기능 필요 x 목록
                            )
                            : audioList.length > 1 ? (
                            	audioList.map((audio, i) => (
                                	<Draggable 
                                    	draggableId={"audioList" + i} 
                                        index={i}
                                    >
                                    	{(provided, snapshot) => (
                                        	<div 
                                            	key={audio.id}
                                            	ref={provided.innerRef}
                                                {...provided.draggableProps}
                                                style={getItemStyle(
                                                	snapshot.isDragging,
                                                    provided.draggableProps.style
                                                )} >
                                                <SubTopicAudioListDnd 
                                                	provided={provided} 
                                                   	data={audio} 
                                                    index={i} 
                                                    handleOpenModal={handleOpenModal} 
                                                />
                                             </div>
                                         )}
                                     </Draggable>
                                     )))
                                    : (
                                        <div className={styles.subAudioNoData}>
                                            <p>오디오가 없습니다. 등록해주세요.</p>
                                            <Button 
                                            	onClick={() => handleOpenModal(data.id, null, true)} 
                                                style={{ background: "#a2b521" }}
                                            >
                                            	등록
                                            </Button>
                                        </div>
                                    )}
                                    {audioList.length >= 1 && 
                                    	<Button 
                                    	    className={styles.addBtn} 
                                        	onClick={() => handleOpenModal(data.id, null, true)} 
                                        	style={{ background: "#a2b521" }}
                                    	>
                                        	추가
                                        </Button>
                                    }
                            </div>
                        </li>
                        {provided.placeholder}
                    </div>
                )}
            </Droppable>
        </DragDropContext>

마지막으로

drag and drop 기능을 js에서는 sortable이라는 jquery 의 라이브러리 (?) 로
어렵지 않게 구현했던바가 있지만, react에서는 라이브러리를 제대로 숙지할 필요성이 있어
처음에는 곤란했따. 특히나 두 리스트를 컨트롤 한다는 부분에 있어서
적절한 예시를 찾지 못했기 때문에 !

내가 기억하고자,,, 마무리,,, 감사합니닷.

0개의 댓글