[React] react-sortable-hoc

@1000peach2022. 04. 26  -  ☕️ 6 min read
[React] react-sortable-hoc

antd 기반 프로젝트에서 사용자가 리스트 내용을 입력하고 드래그 앤 드롭으로 순서를 변경하는 기능을 개발하게 되었다.

작업 비용을 줄이기 위해 드래그에서 라이브러리를 사용하기로 했고 react-sortable-hoc를 선정하였다. 다른 프로젝트의 유사한 기능에 사용한 히스토리가 있어 참고에 용이했으며 러닝커브가 낮다고 느꼈기 때문이다.


1. 사용 방법

1-1. 설치

npm install react-sortable-hoc --save yarn add react-sortable-hoc

1-2. 드래그

1-2-1. 아이템 빈 공간을 잡고 드래그

리스트 컴포넌트를 SortableContainer로 래핑하고 리스트 아이템 컴포넌트를 SortableElement로 래핑하면 간단히 사용할 수 있다.

아이템 드래그

아래는 antd form에 설정된 프로젝트 리스트 값을 Form.List로 출력한 코드이다.
(state로 리스트를 관리하는 예시는 공식 문서에서 제공한다)

App.tsx

import { FC } from 'react' import styled from 'styled-components' import { Button, Form } from 'antd' import { useForm } from 'antd/lib/form/Form' import ItemList from './ItemList' interface Project { title: string description: string } const INIT_PROJECT_LIST: Project[] = [ { title: '[개인] 포트폴리오 블로그 개발', description: 'Gatsby로 블로그를 만들었어요', }, { title: '[팀] 테스트 메이커 서비스 개발', description: '쉽게 테스트를 제작할 수 있는 기능 구현', }, ] const App: FC = () => { const [form] = useForm<Project[]>() return ( <Wrapper> <Form form={form} onFinish={value => console.log(value)}> <Form.List name="projects" initialValue={INIT_PROJECT_LIST}> {(fields, methods) => ( <ItemList fields={fields} helperClass="drag-project-item" useWindowAsScrollContainer onSortEnd={({ oldIndex, newIndex }) => methods.move(oldIndex, newIndex)} /> )} </Form.List> <Button htmlType="submit">완료</Button> </Form> </Wrapper> ) } const Wrapper = styled.div` width: 500px; ` export default App
  • <Form.List>의 children으로 fields(아이템 배열)를 받아 <ItemList>에 전달한다.
  • onSortEnd : 드래그가 끝났을 때 호출되는 콜백. method.move로 순서를 변경한다.
  • helperClass : 드래그중인 아이템에 추가할 클래스 이름
  • useWindowController : 스크롤 컨테이너를 window로 설정한다. 드래그 중인 아이템이 view를 벗어났을 때 window가 스크롤 되기 때문에 좋은 UX를 제공할 수 있고, false(default)로 되어있을 때 간헐적으로 드래그 위치가 맞지 않는 오류가 있었어서 true로 설정하는 것을 권장한다.

App.css

.drag-project-item { background-color: ghostwhite; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); z-index: 100; }

helperClass에 설정한 이름으로 드래그 중인 아이템을 스타일을 설정한다.

ItemList.tsx

import { ComponentClass } from 'react' import { FormListFieldData } from 'antd/lib/form/FormList' import { SortableContainer, SortableContainerProps } from 'react-sortable-hoc' import Item from './Item' interface Props { fields: FormListFieldData[] } const ItemList: ComponentClass<SortableContainerProps & Props> = SortableContainer( ({ fields, ...rest }: Props) => { return ( <ul> {fields.map((fieldItem, index) => ( <Item key={fieldItem.key} fieldItem={fieldItem} index={index} /> ))} </ul> ) }, ) export default ItemList
  • 리스트 컴포넌트는 SortableContainer로 래핑한다. 주의할 점은 fields.map 상위에 element로 래핑해야 한다.
  • <Item>index, key를 필수로 전달한다. 이 때 SortableElement로 래핑한 컴포넌트 내에서 index를 읽을 수 없기 때문에 필요하다면 다른 이름으로 전달해야 한다.

Item.tsx

import React from 'react' import styled from 'styled-components' import { Form, Input } from 'antd' import { FormListFieldData } from 'antd/lib/form/FormList' import { SortableElement, SortableElementProps } from 'react-sortable-hoc' interface Props { fieldItem: FormListFieldData } const Item: React.ComponentClass<SortableElementProps & Props> = SortableElement( ({ fieldItem }: Props) => { const { key, name: fieldName, ...restField } = fieldItem return ( <Wrapper> <Form.Item label="프로젝트 제목" name={[fieldName, 'title']} {...restField}> <Input name="title" /> </Form.Item> <Form.Item label="프로젝트 내용" name={[fieldName, 'description']} {...restField}> <Input name="description" /> </Form.Item> </Wrapper> ) }, ) const Wrapper = styled.li` padding: 20px; border-radius: 5px; ` export default Item

아이템 컴포넌트는 SortableElement로 래핑하고 fieldItem<Form.Item>에 전달한다.

1-2-2. 드래그 핸들러를 잡고 드래그

아이템 내 특정 요소를 잡고 드래그하려면 아이템 컴포넌트에 SortableHandle로 래핑한 드래그 핸들러 컴포넌트를 추가한다.

아이템 드래그

App.tsx

... <ItemList fields={fields} helperClass="drag-project-item" useWindowAsScrollContainer useDragHandle onSortEnd={({ oldIndex, newIndex }) => methods.move(oldIndex, newIndex) } /> ...
  • useDragHandle : 드래그 핸들러를 사용하기 위해 true로 설정한다.

DragHandler.tsx

import { SortableHandle } from 'react-sortable-hoc' const DragHandler = SortableHandle(() => { return <span className="drag-handler">=</span> }) export default DragHandler

SortableHandle로 래핑하여 드래그 핸들러 컴포넌트를 생성한다.

Item.tsx

... const Item: React.ComponentClass<SortableElementProps & Props> = SortableElement( ({ fieldItem }: Props) => { const { key, name: fieldName, ...restField } = fieldItem return ( <Wrapper> <DragHandler /> ... </Wrapper> ) }, ) ...

아이템 컴포넌트 원하는 위치에 <DragHandler>를 추가한다.


2. 마치며

소개한 기능 외에도 react-sortable-hoc다양한 props를 제공한다. 드래그 방향 제한, 트랜지션 설정, 컨테이너 변경, 정렬 콜백 등이 기본으로 있어서 커스텀이 수월했다.
또한 래핑만 하면 동작하기 때문에 다른 드래그 앤 드롭 라이브러리에 비해 사용법이 간단하다. (공식 문서도 이를 강점으로 내세운다)

그러나 react-sortable-hoc에 사용하는 findDOMNode가 더 이상 React에서 채택되지 않아 추후 기능에 영향을 미칠 수 있다. 이를 대비하여 react-sortable-hoc의 모든 기능을 지원하며 더 현대적인 dnd-kit를 추천하고 있다.

개발 기간이 우선이라면 react-sortable-hoc로 구현 후 추후 라이브러리를 교체하거나, 안정성이 우선이라면 dnd-kit 외 다른 라이브러리를 검토하여 적용할 수 있겠다. antd와 사용 가능한 라이브러리는 Antd Design Third-Party Libraries에 나와있다.


🔗 참고

이전 게시글

[회고] Gatsby 블로그 개발 후기

다음 게시글

[Effective Typescript] 1장 타입스크립트 알아보기

profile

@1000peach

    GitHubGmailPortfolio
© 2022 1000peach, Powered By Gatsby.