--- title: TypeScriptのReact上でthree.js情報をWEB Socket同期させる tags: TypeScript React three.js Socket.io websocket author: YutaroYoshikawa slide: false --- HALアドベントカレンダー4日目を担当する、HAL東京IT学部2年のゆうたろうです。 最近勉強したフロントエンドに関する技術をテーマに記事を書いてみます。 ## TypeScriptのReactでthree.js Reactで WEB GLをthree.jsを使って描画させるときに使うライブラリは*react-three-renderer*だと思うのですが、このライブラリをTypeScriptで使おうと思っても型定義ファイルが用意されていなくてエラーを起こしてしまいます。そこで、代わりになるものを探していたところGitHub上に*react-three-renderer-fiber*というリポジトリを見つけて、TypeScriptでも*react-three-renderer*みたいなものを使えるようにしてくれるものがあったのでこれを活用していきます。 ```shell git clone https://github.com/toxicFork/react-three-renderer-fiber cd react-three-renderer-fiber yarn install cd example yarn install cd ../ yarn start ``` 上記のコマンドを実行すると、localhost:8080にWEBサーバが建てられてブラウザでアクセスするとthreejsのサンプルをいろいろ見ることができると思います。 今回はこのサンプルの中の*experiment*というサンプルを使ってWEB Socketを実装していきたいと思います。 ## Socket.ioとthree.jsの連携 WEB Socketを実装するときはSocket.ioというライブラリを使うのがポピュラーなのでこれを使っていきます。 最近AWSのApp SyncでWEB Socketのフルマネージドサービスがリリースされるというニュースをみて、ぜひこれを使ってみたいのですが、まだリリースされていないので普通に自分でサーバを建ててWEB Socketを実装していこうと思います。 ### プロジェクト構成 ```shell mkdir SocketIoServer cd SocketIoServer yarn init -y yarn add express socket.io toutch index.js ``` ```js:index.js var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); var express = require('express'); app.use(express.static(__dirname + '/react-three-renderer-fiber')); app.get('/', (req, res) => { res.sendFile(__dirname + '/react-three-renderer-fiber/examples/experiment.html'); }); io.on('connection', (socket) => { console.log('a user connected'); socket.on('disconnect', () => { console.log('user disconnected'); }); socket.on('senddata', (data) => { io.emit('recevedata', msg); }); }); http.listen(3000, function(){ console.log('listening on *:3000'); }); ``` expressで先ほどgit cloneした*react-three-renderer-fiber*をアクセスできるようにホスティングさせたいので*SocketIoServer*ディレクトリ配下に移動させます。 現段階のディレクトリ構成が以下の通りになります。 ``` SocketIoServer/ ├ node_modules/ │ └ (yarn addした内容) ├ react-three-renderer-fiber/ │ └ (リポジトリの内容) ├ index.js ├ package.json └ yarn.lock(yarnを使っていた場合) ``` ### サンプルのカスタマイズ expressのホスティングの関係でパス関係が変わったので、それに合わせて*socketServer/react-three-renderer-fiber/examples/experiment.html*のなかに記述されている依存関係のファイルとメインの*experiment.js*のパスを変えます。 ```html:experiment.html Hello React!
``` Socket.ioサーバと通信するときは、*socket.io-client*を使っていくので*SocketIoServer/react-three-renderer-fiver/examples/*にインストールします。 ```shell yarn add socket.io-client yarn add --dev @types/socket.io-client ``` 次に*SocketIoServer/react-three-renderer-fiver/examples/src/Experiment.tsx*を編集してSocket.ioと通信できるようにしていきます。 ```tsx:Experiment.tsx import * as React from "react"; import {Component} from "react"; import * as ReactDOM from "react-dom"; import * as THREE from "three"; import React3 from "../../../src"; import {connect} from "socket.io-client"; import ColorCube from "./ColorCube"; ``` 最初にsocket.io-clientのconnectをインポートします。 ```tsx:Experiment.tsx private readonly cameraPosition: any; private readonly onAnimate: (callback: any) => any; private rafRequest: number; private renderer: any; private scene: any; private camera: any; private animateInterval: number; private socketIo: SocketIOClient.Socket; ``` 次にプロパティにsocketIoを追加します。 ```tsx:Experiment.tsx this.renderer = null; this.scene = null; this.camera = null; this.rafRequest = 0; this.socketIo = connect("localhost:3000"); ``` 最後にconstructorの中でsocket.Ioプロパティの値を代入します。 ### socket.ioでリアルタイムにデータ送受信する socket.ioでは受信に*on*メソッド送信に*emit*メソッドを使います。 ```tsx:Experiment.tsx const cube: any = ; if (this.state.wantsResult) { testResult =
Yay
; } else { react3 =
{cube}
; } ``` cube定数の中を編集し、buttonタグを追加して、Socket.ioへのデータ送信ボタンとして使います。 ```tsx:Experiment.tsx public addData = () => { const randomHeight: number = Math.floor(Math.random() * Math.floor(3)); const randomWidth: number = Math.floor(Math.random() * Math.floor(3)); this.setState({ height: randomHeight, width: randomWidth, }); this.socketIo.emit("senddata", { height: randomHeight, width: randomWidth, }); }; ``` addDataメソッドを追加します。 ```tsx:Experiment.tsx public componentDidMount() { console.log("mounted"); this.renderer.render(this.scene, this.camera); this.animateInterval = window.setInterval(() => { this.onAnimate(() => { if (this.rafRequest === 0) { this.rafRequest = requestAnimationFrame(renderFunction); } }); }, 20); const renderFunction = () => { this.renderer.render(this.scene, this.camera); this.rafRequest = 0; }; this.socketIo.on("recevedata", (data: any) => { this.setState({ height: data.height, width: data.width, }); }); } ``` componentDidMountメソッドの中で、データを受信した時の処理を書きます。 ```tsx:Experiment.tsx //state型定義 public state: { cubeRotation: any, height: number, wantsResult: boolean, width: number, }; ``` ```tsx:Experiment.tsx this.state = { cubeRotation: new THREE.Euler(), height: 1, wantsResult: false, width: 1, }; ``` Socket.ioで送受信したデータはstateで管理したいので新しくwidthプロパティとheightプロパティを定義します。 ### 送受信した値をthreejsのキューブに反映させる キューブの主な情報は*sockerServer/react-three-renderer-fiber/examples/src/app/ColorCube.tsx* で管理されているので送受信した値を読み込めるように編集していきます。 ```tsx:ColorCube.tsx export interface IColorCubeProps { rotation: any; height: number; width: number; } ``` ```tsx:ColorCube.tsx public render() { const { color, } = this.state; const { rotation, height, width, } = this.props; return ( ); } ``` propsの値を定数に代入するところで先ほどstateとして定義した、heightとweightを追加します。 ```tsx:ColorCube.tsx ``` ジオメトリーのwidthとheightにpropsの値が反映されるように修正します。 ## 動かしてみる expressからjsを実行できるように、*SocketIoServer/react-three-renderer-fiver*ディレクトリに移ってからexamplesをビルドします。 ```shell yarn build-examples ``` ビルドが終わったら、*SocketIoServer/*ディレクトリに移ってexpressを起動します。 ```shell node . ``` 複数ブラウザを立ち上げて、それぞれ*localhost:3000*にアクセスし、changeボタンを押すことで、リアルタイムに全てのブラウザでキューブの形が同期されていれば成功です。 ## 最後に 今回作ったものは[ここ](http://ec2-13-113-194-0.ap-northeast-1.compute.amazonaws.com:3000/)でみることができます。 慣れない記事の投稿で説明が荒いところがあったと思いますが、最後まで読んでいただきありがとうございました。