React + dat.GUI でササッと入力GUIを実装

最近、ジェネレーティブアートに興味があって
p5.jsとか使ってなにかCGアート作品作りたいなぁと考えています。

そのようなCG作品が沢山投稿されてる[Codepen](https://codepen.io/)っていうサイトがあります
Codepenとか覗いていると、閲覧中によく右上にあるGUIでCGのパラメータを
変更できるパーツがあって、あれって何かなあとずっと疑問に思っていたので調べました。


# dat.GUI とは

![スクリーンショット 2019-12-11 10.21.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192562/c04e1891-0b31-6ac3-75d9-0859f6325ec1.png)


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

Googleのデータアート部門の人が作った、Javascript製の
パラメーター変更のためのグラフィカルで軽量なUIです。

簡単な書き方でササッと入力用GUIを追加できるので、CGアート界隈の人がよく使ってる感じらしいです。

[Tutorial](https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage)

# React + dat.GUI

dat.GUIでJavascriptの変数を変更できるのはわかりました。

Reactは単方向データバインディングなので、
Reactとdat.GUIを組み合わせようと思ったら、dat.GUIのパラメータ変更時にsetState()を実行しないといけません。

そのあたりのノウハウを実践してみたので共有したいと思います。

# プロジェクトを作る

create-react-appでさくっと作ります。
最近はまってるのでTypescriptでやります。

```bash
yarn create react-app react_dat --template typescript
```

# 必要なライブラリを追加

dat.GUIに加えて、
state管理にunstated-nextを使います

- dat.gui
- @types/dat.gui
- unstated-next

```bash
yarn add dat.gui @types/dat.gui unstated-next
```

# unstated-next

**unstated-next** は React Context Hook を使いやすくしたライブラリです。

基本的な概念としては、

- stateを管理・変更通知するContainerオブジェクトを作る
- `<Container.Provider>`でコンポーネントをラップする
-  ラップされた内部では`Container.useContainer()`でstateが使える
- stateに変更があったら、Providerがラップしているコンポーネントツリーを更新する

みたいな感じ？
偉大な先駆者様の解説記事があるのでそちらを見ると良いでしょう
https://qiita.com/kaba/items/b05f680f850dd46548f3

# GUIContainerを作る

React Hooksをたくさん使います。

- useState
- useMemo
    - dat.GUI オブジェクトをMemonizeする(再レンダリングしても同じメモリ位置のオブジェクトを使う)
- useEffect
    - 最初の1回だけguiにプロパティを追加する


```ts:GUI.ts
import dat from 'dat.gui'
import { createContainer } from 'unstated-next'
import { useState, useMemo, useEffect } from 'react'

const GUIContainer = createContainer(() => {
    const [message, setMessage] = useState('dat.gui')
    const [speed, setSpeed] = useState(0.8)
    const [flag, setFlag] = useState(false)
    const [fruit, setFruit] = useState('apple')
    const [number, setNumber] = useState(1)

    const gui = useMemo(() => new dat.GUI(), [])

    useEffect(() => {
        gui.add({ message }, 'message').onChange(value => setMessage(value))
        gui.add({ speed }, 'speed', -5, 5).onChange(value => setSpeed(value))
        gui.add({ flag }, 'flag').onChange(value => setFlag(value))
        gui.add({ fruit }, 'fruit', [
            'apple',
            'orange',
            'grape'
        ]).onChange(value => setFruit(value))
        gui.add({ number }, 'number', {
            one: 1,
            two: 2,
            three: 3
        }).onChange(value => setNumber(value))

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    return { message, speed, flag, fruit, number }
})

export default GUIContainer
```

ちょっと長いファイルになりましたが、dat.GUIの紹介ということで
色々なフォームを詰め込んで見ました。

基本的には、以下のような書式でGUIフォームを追加できます。

```ts:dat
import dat from 'dat.gui'

let gui = new dat.GUI()
gui.add(Object, "プロパティ名").onChange(value => /* code */)
```

.onChange() メソッドは、変更されたvalueを受け取ってなにか処理をするコールバックを登録できます。

このコールバックの中で**setState()**を実行すれば、ReactDOMを再描写できるというわけですね。


# 作ったGUIコンテナのstateを使う

```tsx:App.tsx
import React from 'react'
import GUIContainer from './GUI'

const App: React.FC = () => {
    const { message, speed, flag, fruit, number } = GUIContainer.useContainer()

    return (
        <>
            <p>{message}</p>
            <p>{speed}</p>
            <p>{`${flag}`}</p>
            <p>{fruit}</p>
            <p>{number}</p>
        </>
    )
}

export default () => (
    <GUIContainer.Provider>
        <App />
    </GUIContainer.Provider>
)
```

# 確認してみる

```bash

yarn start
```

`localhost:3000` にブラウザでアクセス

![スクリーンショット 2019-12-11 10.16.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192562/fd081f6d-0b57-b99b-df8d-be90634c7681.png)


# まとめ

dat.GUI と React を組み合わせるノウハウを書いてみました。

dat.GUIはとても手軽に入力用のGUIを追加できるので、
デジタルアート以外でも、仕事でプロトタイピングのwebアプリの入力を試したい
なんてときにも役に立つと思います。

終わり。