Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
17
Help us understand the problem. What is going on with this article?
@foooomio

Material-UI で並べ替え可能なリストを作る

More than 1 year has passed since last update.

Material-UI では、Material Design Guidelines で定められているドラッグ&ドロップで並べ替えられるリストが提供されていません。ドラッグ&ドロップの機能を提供するライブラリと組み合わせることで、これを実現できます。

ドラッグ&ドロップ

React でドラッグ&ドロップを実現するライブラリは数多ありますが、今回は以下の4つを比較・検討しました。箇条書きはあくまで個人的な感想です。

react-dnd

react-dnd/react-dnd: Drag and Drop for React

  • 一番有名どころ。オリジナルの作者は、React や Redux の Dan Abramov (gaearon) 氏。
  • 拡張性が高いので、その気になればいろんなことができそう。一方で、アニメーションなど自力で実装しなければならない部分が多い。
  • 記述量は多め。

react-beautiful-dnd

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

  • Atlassian 製。
  • ソートのアニメーションが優れている。記述量は多め。
  • “Not for everyone” だそうだ。たとえば、今回必要な「軸の固定 (axis locking) 」は、何度か機能提案されているが、UX を損なう (意訳) としてすべて却下されている。
  • ということで、“Not for me” だった。

react-sortable-hoc

clauderic/react-sortable-hoc: A set of higher-order components to turn any list into an animated, touch-friendly, sortable list ✌️

  • 記述量が少なくて済むので、実装が楽。
  • 現在非推奨な findDOMNode が使われていてレガシーな感じ。
  • Material-UI で DragHandle をやろうとするとちょっとハマった (後述)。

react-smooth-dnd

kutlugsahin/react-smooth-dnd: react wrapper components for smooth-dnd

  • Star の数では劣るが、特に問題はなさそう。
  • 記述量は少なくて済む。ハマりポイントもなく、一番素直に実装できた。
  • タッチデバイスだと難ありらしい (Issue が上がっている)。

実装例

react-sortable-hoc で実装してみた例と、react-smooth-dnd で実装してみた例を記載します。(手元で実装したものと CodeSandbox に上げたもので、使用したパッケージのバージョンが異なりますが、動作に問題はありません。)

react-sortable-hoc

Edit Material-UI Sortable List with react-sortable-hoc

使用したパッケージ

  • React v16.8.5
  • Material-UI v3.9.2
  • react-sortable-hoc v1.8.3
  • array-move v2.0.0
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
import arrayMove from 'array-move';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import DragHandleIcon from '@material-ui/icons/DragHandle';

const DragHandle = SortableHandle(() => (
  <ListItemIcon>
    <DragHandleIcon />
  </ListItemIcon>
));

const SortableItem = SortableElement(({ text }) => (
  <ListItem ContainerComponent="div">
    <ListItemText primary={text} />
    <ListItemSecondaryAction>
      <DragHandle />
    </ListItemSecondaryAction>
  </ListItem>
));

const SortableListContainer = SortableContainer(({ items }) => (
  <List component="div">
    {items.map(({ id, text }, index) => (
      <SortableItem key={id} index={index} text={text} />
    ))}
  </List>
));

const SortableList = () => {
  const [items, setItems] = useState([
    { id: '1', text: 'Item 1' },
    { id: '2', text: 'Item 2' },
    { id: '3', text: 'Item 3' },
    { id: '4', text: 'Item 4' }
  ]);

  const onSortEnd = ({ oldIndex, newIndex }) => {
    setItems(items => arrayMove(items, oldIndex, newIndex));
  };

  return (
    <SortableListContainer
      items={items}
      onSortEnd={onSortEnd}
      useDragHandle={true}
      lockAxis="y"
    />
  );
};

ReactDOM.render(<SortableList />, document.getElementById('root'));

ハマった点ですが、ドラッグ中の li 要素が document.body にクローンされるため、リストのマーカーが表示されてしまって、表示崩れを起こしてしまいました。これを回避するにはいくつかの方法があります。

  • bodylist-style-type: none; を適用する。
  • ListItemSecondaryActionSortableHandle に含めてしまう。
  • ListItemContainerComponent="div" を指定する。

実装例では3番目の方法を採用しています。ListItem だけ変更すると、実際の DOM で ul の直下に div がきて気持ち悪いので、List にも component="div" を指定しています。

react-smooth-dnd

Edit Material-UI Sortable List with react-smooth-dnd

使用したパッケージ

  • React v16.8.5
  • Material-UI v3.9.2
  • react-smooth-dnd v0.8.2
  • array-move v2.0.0
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { Container, Draggable } from 'react-smooth-dnd';
import arrayMove from 'array-move';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import DragHandleIcon from '@material-ui/icons/DragHandle';

const SortableList = () => {
  const [items, setItems] = useState([
    { id: '1', text: 'Item 1' },
    { id: '2', text: 'Item 2' },
    { id: '3', text: 'Item 3' },
    { id: '4', text: 'Item 4' }
  ]);

  const onDrop = ({ removedIndex, addedIndex }) => {
    setItems(items => arrayMove(items, removedIndex, addedIndex))
  };

  return (
    <List>
      <Container dragHandleSelector=".drag-handle" lockAxis="y" onDrop={onDrop}>
        {items.map(({ id, text }) => (
          <Draggable key={id}>
            <ListItem>
              <ListItemText primary={text} />
              <ListItemSecondaryAction>
                <ListItemIcon className="drag-handle">
                  <DragHandleIcon />
                </ListItemIcon>
              </ListItemSecondaryAction>
            </ListItem>
          </Draggable>
        ))}
      </Container>
    </List>
  );
};

ReactDOM.render(<SortableList />, document.getElementById('root'));

先述の通り、現バージョンではタッチデバイスで難ありらしいですが、PC 向けにはこれで問題ないように思います。

余談

いずれ React Hooks を使って useDragAndDrop みたいに書けるようになるかもしれないですね。実装が楽になって、見通しがよくなるといいですね。

17
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
foooomio
Zenn >> https://zenn.dev/foooomio

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
17
Help us understand the problem. What is going on with this article?