見出し画像

p5jsとReactの連携にReduxを使う

p5jsという、Cavas上での図形描画に特化したライブラリがあります。

コイツとReactをうまく連携させることができないか考えたところ、Reduxが便利じゃね?と思ったのでサンプルスクリプトを作ってみました。

サンプル

サンプルの内容

画面の上半分がp5js、下半分がReactコンポーネントになっています。

まず、マウスの座標をp5jsのキャンバス上で動かすと、円の輝度を変更でき、輝度の値がReactコンポーネント上にも示されます。

この輝度の値はReduxで管理しています。

次に、[小さくする]ボタンや[大きくする]ボタンを押すと、円の大きさを変えることができます。

この円の大きさの値もReduxで管理しています。

状態をReduxで管理する

p5jsの中からは、ReduxのStoreの中身を

  • Selector(store.getState()) で読み込み

  • store.dispatch(Action) で書き込む

ように、storeのメソッドを直接呼び出せばいいのです。

Hookを使わずに値を読み込んでしまうのはちょっとアウトローな感じかもしれませんが、こう書くのが一番楽でした~

Storeを作成

Redux Toolkitでサッと作ってしまいます。

//circleSlice.js
import { createSlice, createSelector } from "@reduxjs/toolkit";
import rgb from "hsv-rgb";

const initialState = {
  size: 100,
  brightness: 0.8
};
const circleSlice = createSlice({
  name: "circle",
  initialState,
  reducers: {
    increaseSize: (state) => {
      state.size = state.size * 1.1;
    },
    reduceSize: (state) => {
      state.size = state.size * 0.9;
    },
    setBrightness: (state, { payload }) => {
      state.brightness = payload;
    }
  }
});

export const { increaseSize, reduceSize, setBrightness } = circleSlice.actions;

export const selectColor = createSelector(
  (state) => state.circle,
  (circle) => rgb(342, 85, 255 * circle.brightness)
);
export default circleSlice.reducer;
//store.js
import { configureStore } from "@reduxjs/toolkit";
import circleReducer from "./circleSlice";
export default configureStore({
  reducer: {
    circle: circleReducer
  }
});

p5js

p5jsをReactに持ってくる際は、react-p5-wrapperを使います。

storeのメソッドを直接呼び出して、読み込みや書き込みを行います。

//P5View.js
import { P5Instance, ReactP5Wrapper } from "react-p5-wrapper";
import store from "./store";
import { selectColor, setBrightness } from "./circleSlice";

/**
 *
 * @param {P5Instance} _
 */
const sketch = (_) => {
  _.setup = () => {
    _.createCanvas(200, 200);
    _.noStroke();
    _.frameRate(5);
  };
  _.draw = () => {
    _.background("grey");
    const color = selectColor(store.getState()); #読み込み
    _.fill(color);
    const size = store.getState().circle.size; #読み込み
    _.circle(100, 100, size);
    const brightness = _.map(_.mouseX, 0, _.width, 0, 1, true);
    store.dispatch(setBrightness(brightness)); #書き込み
  };
};

export default () => <ReactP5Wrapper sketch={sketch} />;

一応、ReactP5Wrapperコンポーネントからsketch関数のスコープ内にpropsを引き込む方法はあるのですが、変数が多くなってごちゃごちゃして大変だったので特にpropsは何も渡していません。

普通のReactコンポーネント

普通のReactコンポーネントについては普通に正攻法でReduxを使います。

//ReactComponentView.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { increaseSize, reduceSize } from "./circleSlice";

export default () => {
  const dispatch = useDispatch();
  const brightness = useSelector((state) => state.circle.brightness);
  return (
    <div>
      <button onClick={() => dispatch(reduceSize())}>小さくする</button>
      <button onClick={() => dispatch(increaseSize())}>大きくする</button>
      <p>輝度:{brightness}</p>
    </div>
  );
};

完成

完成した2つのReactコンポーネントを並べて完成です。

import { Provider } from "react-redux";
import store from "./store";
import P5View from "./P5View";
import ReactComponentView from "./ReactComponentView";

export default function App() {
  return (
    <Provider store={store}>
      <P5View />
      <ReactComponentView />
    </Provider>
  );
}

いいなと思ったら応援しよう!