LoginSignup
35
24

More than 3 years have passed since last update.

"react-beautiful-dnd" で実装する React の Drag&Drop with TypeScript, 関数コンポーネント

Last updated at Posted at 2019-12-20

はじめに

React で自前の Drag&Drop (DnD) を実装するのは、なかなか大変だと思います。
ライブラリを探していたところ、Atlassian 製の react-beautiful-dnd というライブラリがよさそうだったので使ってみての所感を書きます。

公式サンプル

縦方向リストの DnD
横方向リストの DnD
関数コンポーネントで実装
2カラム間の DnD

ヌルヌル動いて気持ち〜〜〜

dnd_sample_official.gif

手元で実装してみる

バージョン

"typescript": "^3.7.3",
"react": "^16.12.0",
"react-beautiful-dnd": "^12.2.0",
"styled-components": "^4.4.1"

作ったのはこれ

dnd_sample_kats.gif
(見た目はほぼサンプルと同じだねとか、、聞こえてますよ、、ええ)

↓ディレクトリ構成など分かりやすいようにアレンジしてあるので参考にしてみてください↓
https://github.com/kk-web/react-beautiful-dnd_sample

インストール

※ React まわりの構築は割愛します。

まずは、モジュールをインストール!

npm install --save react-beautiful-dnd

元になるリストを作る

src/App.tsx
import React from "react";
import { ItemType } from "./types";
import List from "./List";

const App = () => {
  const initial: ItemType[] = Array.from({ length: 10 }, (v, k) => k).map(k => {
    return {
      id: `id-${k}`,
      content: `Item ${k}`
    };
  });
  return <List items={initial} />;
};

export default App;
src/types.ts
export type ItemType = {
  id: string;
  content: string;
};
src/List.tsx
import React from "react";
import Item from "./Item";

const List = ({ items }) => (
  <>
    {items.map(item => (
      <Item item={item} key={item.id} />
    ))}
  </>
);

export default List;
src/Item.tsx
import React from "react";
import styled from "styled-components";

const StyledItem = styled.div`
  width: 200px;
  border: 1px solid grey;
  margin-bottom: 8px;
  background-color: lightblue;
  padding: 8px;
`;

const Item = ({ item }) => {
  return <StyledItem>{item.content}</StyledItem>;
};

export default Item;

型定義

ドキュメントの型定義セクションには、

Typescript
If you are using TypeScript you can use the community maintained DefinitelyTyped type definitions. Installation instructions.

とあります。型定義モジュールもインストールしておきましょう。

npm install --save @types/react-beautiful-dnd

Here is an example written in typescript.

サンプルも用意されています!

DnD 実装

src/App.tsx
import React, { useState } from "react";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import { ItemType } from "./types";
import List from "./List";

const reorder = (
  list: ItemType[],
  startIndex: number,
  endIndex: number
): ItemType[] => {
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);

  return result;
};

const App = () => {
  const initial: ItemType[] = Array.from({ length: 10 }, (v, k) => k).map(k => {
    return {
      id: `id-${k}`,
      content: `Item ${k}`
    };
  });
  const [state, setState] = useState({ items: initial });

  const onDragEnd = result => {
    if (!result.destination) {
      return;
    }

    if (result.destination.index === result.source.index) {
      return;
    }

    const items = reorder(
      state.items,
      result.source.index,
      result.destination.index
    );

    setState({ items });
  };

  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId="list">
        {provided => (
          <div ref={provided.innerRef} {...provided.droppableProps}>
            <List items={state.items} />
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
};

export default App;

↓変更ありません

src/types.ts
export type ItemType = {
  id: string;
  content: string;
};
src/List.tsx
import React from "react";
import Item from "./Item";

const List = React.memo<{ items }>(({ items }) => (
  <>
    {items.map((item, index: number) => (
      <Item item={item} index={index} key={item.id} />
    ))}
  </>
));

export default List;
src/Item.tsx
import React from "react";
import styled from "styled-components";
import { Draggable } from "react-beautiful-dnd";

const StyledItem = styled.div`
  width: 200px;
  border: 1px solid grey;
  margin-bottom: 8px;
  background-color: lightblue;
  padding: 8px;
`;

const Item = ({ item, index }) => {
  return (
    <Draggable draggableId={item.id} index={index}>
      {provided => (
        <StyledItem
          ref={provided.innerRef}
          {...provided.draggableProps}
          {...provided.dragHandleProps}
        >
          {item.content}
        </StyledItem>
      )}
    </Draggable>
  );
};

export default Item;

Server Side Rendering

Next.js で実装したときにつまづきました、、

Drag できなくて、console を見てみると

react-beautiful-dnd A setup problem was encountered.
> Invariant failed: Draggable[id: id-9]: Unable to find drag handle👷‍ This is a development only message. It will be removed in production builds.

とエラーが出ていました。
ドキュメントを見てみると

API 🏋
(中略)
resetServerContext() - Utility for server side rendering (SSR)

と記載が!
SSR の場合は、任意の場所でこの関数を実行しましょう!

おわりに

DnD を自作で実装しようとすると手間がかかりますが、react-beautiful-dnd を使えば数行の実装用関数と state や props を設定するだけで、ヌルヌル動くものができるのはありがたいですね!
公式サンプルではコンポーネントが1つのファイルにまとめられていますが、今回の実装のようにリストやアイテムで分解すれば、既存のリストにも適用しやすいのではないかと思います。

35
24
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
24