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

Reactアプリケーションにドラッグ&ドロップを実装するのが[React DnD](https://react-dnd.github.io/react-dnd/)です。公式サイトの「[Examples](https://react-dnd.github.io/react-dnd/examples)」には、作例がいくつかに分類されて掲載されています。

その中でも基本的な「Drag Around」にある「[Naive](https://react-dnd.github.io/react-dnd/examples/drag-around/naive)」のサンプルのつくり方と構文を、記事「[Create React App + React DnD: 単純なドラッグ&ドロップ](https://www.fumiononaka.com/Business/html5/FN2106002.html)」で解説しました。でき上がった作例「[React DnD: Drag around naive](https://codesandbox.io/s/react-dnd-drag-around-naive-50lny)」にTypeScriptの型づけを加えてみようというのが本稿のお題です。

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

# React + TypeScriptのひな形に作例のコードをコピーする
Reactアプリケーションのひな形は、[Create React App](https://ja.reactjs.org/blog/2016/07/22/create-apps-with-no-configuration.html)でつくります。React + TypeScriptのひな形とするオプションは`--template typescript`です。プロジェクト名は`react-dnd-drag-around-naive`としました。

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

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

```terminal
npm install react-dnd react-dnd-html5-backend
```

そうしたら、作例「[React DnD: Drag around naive](https://codesandbox.io/s/react-dnd-drag-around-naive-50lny)」からコードをひな形にコピーします。ひな形に新たに加えなければならないのは、つぎの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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/7ddfc2f2-4bdb-47a6-42b8-45a404508607.png)

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


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

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

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

};
```

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

```react: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`にインタフェースとして定めました。あとあと、他のモジュールからも参照して用いられるかもしれないからです。

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

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

さらに、インタフェース`BoxState`を定め、`useState`フックにジェネリックで型指定しました。これで、前掲作例がTypeScriptのプロジェクトに書き替えられました(図002)。サンプルは[CodeSandboxに公開](https://codesandbox.io/s/react-dnd-typescript-drag-around-naive-ejw80)しますので、各モジュールのコードや動きはこちらでお確かめください。

```react: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/df136050-d372-8060-61be-939c005685e2.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-dnd-typescript-drag-around-naive-ejw80)


# 公式サイトのTypeScript作例との違い
React DnD公式サイトのDrag Around「[Naive](https://react-dnd.github.io/react-dnd/examples/drag-around/naive)」にも[TypeScriptの作例](https://codesandbox.io/s/github/react-dnd/react-dnd/tree/gh-pages/examples_hooks_ts/02-drag-around/naive?from-embed=&file=/src/Container.tsx)が公開されています。モジュール`src/Container.tsx`について、本稿のコードと異なる部分をふたつだけ補いましょう。

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

```react: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`型のデータから必要なプロパティを[分割代入](https://qiita.com/FumioNonaka/items/58358a29850afd7a0f37#%E5%88%86%E5%89%B2%E4%BB%A3%E5%85%A5%E3%82%92%E4%BD%BF%E3%81%86)で取り出したことです。細かい点ではあるものの、コードがスッキリします。

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