【React】値を操作するコントローラーパッケージ（LEVA・dat.GUI）

<!-- ========================================= -->
# 概要
<!-- ========================================= -->
ReactでWebアプリケーションを作成していると、値を操作するコントローラーが欲しくなるときがあります。
ちょっと動作を確認したいときに、**LEVA・dat.GUI**を使用すると簡単にコントローラーが実装できます。


:::note info
<strong>追記（2021.10.28）</strong>
LEVAに関して、もう少し調査をして扱えるインターフェイスがdat.GUIと遜色ないとわかったので、改めてまとめました。
:::

https://qiita.com/nemutas/items/a5efdd1e8f2040f6e6e5

### どちらがいいのか
インターフェイスの種類は、**dat.GUI**の方が充実してる印象です。
**LEVA**は、インターフェイスの種類では劣りますが、使い方がdat.GUIより簡単です。

:::note info
使いたいインターフェイスの種類によって、パッケージを使い分けするのがいいと思います。
:::

<!-- ========================================= -->
# LEVA
<!-- ========================================= -->
### ドキュメント
https://github.com/pmndrs/leva

### インストール
```shell
npm i leva
```

### インターフェースの種類
![スクリーンショット 2021-10-25 122839.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/635dd92b-0fb7-9e6d-7d05-dd85cf93f77a.png)
※ インターフェイスの名前は自由に付けられます。

https://github.com/pmndrs/leva/blob/master/docs/inputs.md

### コード
```.tsx
import { rgba } from 'csx';
import { useControls } from 'leva';
import React, { useEffect, useRef, VFC } from 'react';
import { css } from '@emotion/css';

export const LevaController: VFC = () => {
	const leftContainerRef = useRef<HTMLDivElement>(null)
	const rightContainerRef = useRef<HTMLDivElement>(null)

	const props = useControls({
		LeftColor: '#C23841',
		RightColor: { r: 61, g: 199, b: 190, a: 0.5 },
		Text: 'hogehoge',
		Toggle: true,
		Number: {
			value: 4,
			min: 0,
			max: 10,
			step: 1
		},
		Interval: {
			min: 0,
			max: 10,
			value: [4, 5]
		},
		Position: { x: 0, y: 0 },
		BoxSize: {
			value: [10, 20],
			joystick: false
		}
	})

	const colorToString = (color: { r: number; g: number; b: number; a: number }) => {
		return rgba(color.r, color.g, color.b, color.a).toString()
	}

	useEffect(() => {
		leftContainerRef.current!.style.backgroundColor = props.LeftColor
		rightContainerRef.current!.style.backgroundColor = colorToString(props.RightColor)
	}, [props])

	return (
		<div className={styles.container}>
			<div ref={leftContainerRef} className={styles.leftContainer}></div>
			<div ref={rightContainerRef} className={styles.rightContainer}></div>
		</div>
	)
}

const styles = {
	container: css`
		position: relative;
		width: 100%;
		height: 100%;
		display: flex;
	`,
	leftContainer: css`
		position: relative;
		width: 50%;
		height: 100%;
	`,
	rightContainer: css`
		position: relative;
		width: 50%;
		height: 100%;
	`
}
```

* **useControls**を呼びだすことによって、自動的にコントローラーが表示されるようになります。
* Colorのインターフェイスで`透過`を扱いたい場合は、**rgba**で初期値を指定します。

```.tsx
RightColor: { r: 61, g: 199, b: 190, a: 0.5 }
```
![スクリーンショット 2021-10-25 123827.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/ffc0c5f0-29df-5d13-c051-4187cacaa6d8.png)

コントローラー上ではHexColorとして表示されますが、受け取れる値（props.RightColor）は、rgbaのオブジェクトとなります。

### 外部からコントローラーの値を変更したい場合

https://github.com/pmndrs/leva/blob/master/docs/advanced/controlled-inputs.md

<!-- ========================================= -->
# dat.GUI
<!-- ========================================= -->
### ドキュメント

https://github.com/dataarts/dat.gui

https://github.com/dataarts/dat.gui/blob/master/API.md

### インストール
```shell
npm i dat.gui
npm i -D @types/dat.gui
```

### インターフェースの種類
![スクリーンショット 2021-10-25 124549.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358498/fe5d3f76-1fef-d78f-9dde-d278fd3a217a.png)
※ インターフェイスの名前は自由に付けられます。

### コード
dat.GUIはReact用のパッケージではないので、すこしだけ実装量が増えます。
コードが長くなるのでカスタムフックにしています。

```.tsx
import React, { useEffect, useRef, VFC } from 'react';
import { css } from '@emotion/css';
import { useDatGuiController } from './useDatGuiController';

export const DatGuiController: VFC = () => {
	// コントローラーの作成
	const { values } = useDatGuiController()

	const containerRef = useRef<HTMLDivElement>(null)

	useEffect(() => {
		containerRef.current!.style.backgroundColor = values.colorHex
	}, [values.colorHex])

	return (
		<div ref={containerRef} className={styles.container}>
			<div
				className={
					styles.text
				}>{`group x：${values.group.x}, y：${values.group.y}, z：${values.group.z}`}</div>
		</div>
	)
}

const styles = {
	container: css`
		position: relative;
		width: 100vw;
		height: 100vh;
		display: flex;
		justify-content: center;
		align-items: center;
	`,
	text: css`
		color: #fff;
		font-size: 5rem;
	`
}
```

```.tsx:useDatGuiController.tsx
import * as dat from 'dat.gui';
import { useEffect, useState } from 'react';

const props = {
	colorHex: '#1e1e1e',
	colorRGBA: [0, 128, 255, 0.3],
	text: 'hoge',
	number: 5,
	check: true,
	select: 'foo',
	group: {
		x: 0,
		y: 0,
		z: 0
	}
}

export const useDatGuiController = () => {
	const [values, setValues] = useState(props)

	useEffect(() => {
		const gui = new dat.GUI()
		// object key name = prop name
		gui.addColor(props, 'colorHex').onChange(e =>
			setValues(prev => {
				return { ...prev, colorHex: e }
			})
		)
		gui.addColor(props, 'colorRGBA').onChange(e =>
			setValues(prev => {
				return { ...prev, colorRGBA: e }
			})
		)
		gui.add(props, 'text').onChange(e =>
			setValues(prev => {
				return { ...prev, text: e }
			})
		)
		gui.add(props, 'number', 0, 10, 0.1).onChange(e =>
			setValues(prev => {
				return { ...prev, number: e }
			})
		)
		gui.add(props, 'check').onChange(e =>
			setValues(prev => {
				return { ...prev, check: e }
			})
		)
		gui.add(props, 'select', ['foo', 'bar', 'hoge']).onChange(e =>
			setValues(prev => {
				return { ...prev, select: e }
			})
		)
		gui.add(
			{
				button: () => console.log('clicked!')
			},
			'button'
		)

		// グループ化
		const group = gui.addFolder('group')
		group.add(props.group, 'x', -100, 100, 1).onChange(e =>
			setValues(prev => {
				return { ...prev, group: { ...prev.group, x: e } }
			})
		)
		group.add(props.group, 'y', -100, 100, 1).onChange(e =>
			setValues(prev => {
				return { ...prev, group: { ...prev.group, y: e } }
			})
		)
		group.add(props.group, 'z', -100, 100, 1).onChange(e =>
			setValues(prev => {
				return { ...prev, group: { ...prev.group, z: e } }
			})
		)

		return () => {
			group.destroy()
			gui.destroy()
		}
	}, [])

	return { values }
}
```
* **new dat.GUI()**をすることで、自動的にコントローラーが表示されるようになります。
* インスタンスを作成して、それぞれインターフェイスを追加していきます。

```.tsx
const gui = new dat.GUI()
// object key name = prop name
gui.addColor(props, 'colorHex').onChange(e =>
	setValues(prev => {
		return { ...prev, colorHex: e }
	})
)
```

* 色を扱う場合は**addColor**、グループ化する場合は**addFolder**、その他は**add**関数を使用します。
* 第1引数には**Object（props）**をとります。初期値になっています。第2引数には対応する**key名**を指定します。
* Colorインターフェイスでは、`透過`も扱えますが、コントローラー上で値を変更することができませんでした。