LoginSignup
4
2

More than 1 year has passed since last update.

React DnD + TypeScript: 単純なドラッグ&ドロップ

Posted at

Reactアプリケーションにドラッグ&ドロップを実装するのがReact DnDです。公式サイトの「Examples」には、作例がいくつかに分類されて掲載されています。

その中でも基本的な「Drag Around」にある「Naive」のサンプルのつくり方と構文を、記事「Create React App + React DnD: 単純なドラッグ&ドロップ」で解説しました。でき上がった作例「React DnD: Drag around naive」にTypeScriptの型づけを加えてみようというのが本稿のお題です。

React DnDの構文や作例のコードの組み立てについては、リンクした前出の記事をお読みください。

React + TypeScriptのひな形に作例のコードをコピーする

Reactアプリケーションのひな形は、Create React Appでつくります。React + TypeScriptのひな形とするオプションは--template typescriptです。プロジェクト名はreact-dnd-drag-around-naiveとしました。

npx create-react-app react-dnd-drag-around-naive --template typescript

また、React DnDとバックエンドのreact-dnd-html5-backendもインストールしてください(「Create React App + React DnD 02: ドラッグ&ドロップで動かす」01「コンポーネントをドラッグする」参照)。

npm install react-dnd react-dnd-html5-backend

そうしたら、作例「React DnD: Drag around naive」からコードをひな形にコピーします。ひな形に新たに加えなければならないのは、つぎの3つのモジュールです。拡張子はJavaScriptのjsxでなく、TypeScriptのtsxに変えます。

  • src/Box.tsx
  • src/Container.tsx
  • src/Example.tsx

モジュールsrc/App.tsxは、すでにひな形にありますので、つくりません。単に、作例からコードを上書きペーストするだけです。ルートモジュールは、これでできあがりました(図001)。コンポーネントAppに引数はありませんし、戻り値の型はJSX(JSX.Element)だと推論されるからです。

図001■ルートモジュールsrc/App.tsx

2106001_001.png

3つのモジュールについても、作例からまずはコードをそのままコピー&ペーストしてください。

モジュールsrc/Example.tsxsrc/Box.tsx

3つのモジュールのうち、src/Example.tsxsrc/Box.tsxを先に採り上げます。コピーしたコードから、TypeScriptにしたがって書き替える部分を絞って見ていきましょう。

src/Example.tsxは、関数コンポーネントExampleの型をReact.VFCで定めるだけです。もっとも、前掲ルートコンポーネントAppと同じく、引数がなく戻り値のJSXは推論できます。したがって、型指定しなくても構いません。本稿のお題がTypeScriptによる型づけなので、ここは加えておくことにしましょう。

src/Example.tsx
export const Example: React.VFC = () => {

};

モジュールsrc/Box.tsxには、JSX要素のstyleプロパティに与えるオブジェクトの定め(style)があります。その型づけはReact.CSSPropertiesです。また、Boxコンポーネントは、プロパティを引数に受け取ります。したがって、インタフェースBoxPropsを定義して、関数コンポーネントの型React.VFCにジェネリックで与えました。

src/Box.tsx
const style: React.CSSProperties = {

};

export interface BoxProps {
    id: string;
    left: number;
    top: number;
    hideSourceOnDrag?: boolean;
    children: React.ReactNode;
}

export const Box: React.VFC<BoxProps> = ({

}) => {

}

モジュールsrc/Container.tsx

今回、手を加える部分が多いのは、モジュールsrc/Container.tsxです。

ドラッグする要素の仕様オブジェクトの型(DragItem)は、新たなモジュールsrc/interfaces.tsにインタフェースとして定めました。あとあと、他のモジュールからも参照して用いられるかもしれないからです。

src/interfaces.ts
export interface DragItem {
    type: string;
    id: string;
    top: number;
    left: number;
}

モジュールsrc/Container.tsxは、前掲インタフェースDragItemに加え、react-dndから型XYCoordimportします。用いられるのはいずれも、useDropの引数コールバックが返す仕様オブジェクトのdrop()メソッドです。メソッドの第1引数(item)がDragItemで型づけられ、getDifferenceFromInitialOffset()の戻り値をXYCoord型アサーションします。

さらに、インタフェースBoxStateを定め、useStateフックにジェネリックで型指定しました。これで、前掲作例がTypeScriptのプロジェクトに書き替えられました(図002)。サンプルはCodeSandboxに公開しますので、各モジュールのコードや動きはこちらでお確かめください。

src/Container.tsx
// import { useDrop } from "react-dnd";
import { useDrop, XYCoord } from "react-dnd";

import { DragItem } from "./interfaces";

export interface ContainerProps {
    hideSourceOnDrag: boolean;
}
export interface BoxState {
    [key: string]: { top: number; left: number; title: string };
}
const styles: React.CSSProperties = {

};
export const Container: React.VFC<ContainerProps> = ({ hideSourceOnDrag }) => {
    const [boxes, setBoxes] = useState<BoxState>({

    });

    const [, drop] = useDrop(
        () => ({

            drop(item: DragItem, monitor) {
                const { x, y } = monitor.getDifferenceFromInitialOffset() as XYCoord;

    );

}

図002■React DnD + TypeScript: 単純なドラッグ&ドロップ

FN2106002_003.png
>> CodeSandboxへ

公式サイトのTypeScript作例との違い

React DnD公式サイトのDrag Around「Naive」にもTypeScriptの作例が公開されています。モジュールsrc/Container.tsxについて、本稿のコードと異なる部分をふたつだけ補いましょう。

ひつは、インタフェースです。公式サイトの作例には、つぎのようなContainerStateが定められています。けれど、どこにも用いられていません。おそらく、useStateフックのジェネリックに使おうとしたのではないかと推測します。けれど、実際に書き加えられているのは型注釈です。

src/Container.tsx
export interface ContainerState {
    boxes: { [key: string]: { top: number; left: number; title: string } }
}

export const Container: FC<ContainerProps> = ({ hideSourceOnDrag }) => {
    const [boxes, setBoxes] = useState<{
        [key: string]: {
            top: number
            left: number
            title: string
        }
    }>({

    })

}

本項では前掲コードのとおり、インタフェースBoxStateを定めてuseStateのジェネリックに加えました。

もうひとつは、XYCoord型のデータから必要なプロパティを分割代入で取り出したことです。細かい点ではあるものの、コードがスッキリします。

src/Container.tsx
// const delta = monitor.getDifferenceFromInitialOffset() as XYCoord  // 公式作例
const { x, y } = monitor.getDifferenceFromInitialOffset() as XYCoord;
4
2
0

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
4
2