ラベル React.js の投稿を表示しています。 すべての投稿を表示
ラベル React.js の投稿を表示しています。 すべての投稿を表示

2018年11月15日

Create React AppでWeb Workerを使うには

「Web Worker」を使うと、ブラウザ上のJavascriptでも複数のスレッドを使うことが出来るようになります。


Web Worker を使用する - Web API | MDN https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Using_web_workers#Web_Workers_API


これによって、CPU負荷の高い計算処理などを別スレッドで実行することで、画面の反応が鈍くなったりするのを防ぐことが出来ます。


今回は、Create React Appで作成したプロジェクトでWeb Workerを使う方法を調査しました。


1. publicフォルダにWorkerのJSファイルを配置して読み込む。


⇒ 最も基本的で単純な方法です。publicフォルダに配置したファイルはそのままデプロイされるので下の方法で素直に読み込んで使うことが出来ます。

const myWorker = new Worker("worker.js");
myWorker.postMessage("hoge");


短所としては、TypeScriptやES2017などを使っている場合は別途自分でビルドしてpublicフォルダに配置する必要があることが挙げられます。


2. ejectしてからWebPackの設定に worker-loader または worker-plugin を追加する。


⇒ worker-loaderを使うと、別スレッドで動かしたい処理をModuleとして作成しておいて、使いたい箇所でそれをimportしてnewするだけでWeb Workerとして動作するようになります。コード例は、下のようになります。

import HelloWorker from './hello.worker.js';
const helloWorker = new HelloWorker();
helloWorker.postMessage('hoge');

詳細はこちら。
Add support for WebWorker with worker-loader (#3660) by iansu · Pull Request #3934 · facebook/create-react-app 

worker-plugin を使う場合はこんな感じ。

const worker = new Worker('./worker', { type: 'module' });
worker.postMessage('hoge');

詳細はこちら。
Add support for WebWorker with worker-plugin by Krivega · Pull Request #5464 · facebook/create-react-app  




3. ejectせずに react-app-rewired を使ってWebPackの設定に worker-loader または worker-plugin を追加する。


⇒ 下のブログ記事にejectせずに上の2と同じ事をする方法が書かれていました。

How to use web workers with react-create-app and not ejecting in the attempt

ただ残念ながら、react-app-rewiredは最近のVersionには非対応なのでこの方法は現在は使えないようです。


4. WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する。


⇒ 下のissueへのコメントで紹介されている方法です。

Is it possible to use load webworkers? · Issue #1277 · facebook/create-react-app

関数をtoString()で文字列化して、さらにそれをBlobにします。こうすると、WorkerのコンストラクタにObjectURLとして渡してWorkerインスタンスを生成出来るようです。

let code = worker.toString();
code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}"));
const blob = new Blob([code], {type: "application/javascript"});
const worker = new Worker(URL.createObjectURL(blob));

少々トリッキーですが、試してみたところ少なくともMacのChrome/Safari/FireFoxでは問題なく動いています。

この方法なら、WebPackの設定を変更する必要がありません。また上記1のようにWorkerのJSファイルだけ自前でビルド・配置したりする必要もありません。

具体的なコード例は後述します。


5. 公式にサポートされるまで待つ。


⇒ 上の2で挙げた方法がPull Requestとして上がっており公式にも検討されているみたいですが、どうやらブラウザ互換性の問題(?)があるようで2018年11月時点ではまだマージされるには至っていません。将来的には公式にサポートされる可能性はあるので、気には止めておいた方が良いかも知れません。

Add support for WebWorker with worker-loader (#3660) by iansu · Pull Request #3934 · facebook/create-react-app 

Add support for WebWorker with worker-plugin by Krivega · Pull Request #5464 · facebook/create-react-app 



今回は上記4の方法を試してみたので、以下に紹介しておきます。

WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成するサンプル


index.jsと同じフォルダにcreateWorker.js, myWorker.jsの2つのファイルを作成します。

createWorker.js

---

myWorker.js

---


createWorkerとmyWorkerをimportして、下のようにすることでWorkerスレッドが使用可能になります。

import createWorker from './createWorker';
import myWorkerFunc from './myWorker';
const myWorker = createWorker(myWorkerFunc);


index.js

---









2018年11月4日

React/TypeScriptでリバーシゲームを作る (2) - ゲームロジックの実装

前回はボード上に石を配置するまでを実装しましたが、今回はさらにリバーシのルールを実装して実際に遊べるようにしたいと思います。


実際に動いているものはこちらで試すことが出来ます。

https://reversi-ocmiwdgcdj.now.sh



ゲームのルールを実装するには、大まかに分けて次の4つの処理が必要になります。


  1. 「次に石を配置可能な場所」の判定と表示
  2. 挟まれた石を裏返す
  3. 石を置ける場所が無い場合の「パス」処理
  4. ゲーム終了判定と勝敗の表示


1. 「次に石を配置可能な場所」の判定と表示


各手番が始まったときに、ボード上の全てのCellをループして次の処理を行います。


  • 現在のCellからタテ・ヨコ・ナナメ全ての方向に向かってひとつずつ次のCellの色を確認する。
    • 相手の石が置かれていたら、一時的な配列(arr)にそのCellの座標をプッシュして、さらにその方向の次のCellを確認。
    • 自分の石が置かれていたら、その方向への探索を終了。
    • 空白なら、その方向への探索を終了。
    • 座標がボードの範囲外に出た場合は、その方向への探索を終了。
  • 上の処理で最後に自分の石が見つかった場合で、かつ配列arrに要素が含まれている場合は、「挟める対象の相手の石が見つかった」ということなので、そのCellを「自分の石を配置可能なCell」としてマークしておく。(cell.placeableプロパティにtrueをセットする。)


Cellコンポーネントでは、対応するCellオブジェクトのplaceableプロパティが属性として渡されてくるので、その値がtrueの場合は、小さな黄色の円をSVGのcircleタグを使って表示するようになっています。


2. 挟まれた石を裏返す


プレーヤーがCellをクリックしたときには、CellコンポーネントのhandleClick()メソッドが実行され、さらにstoreのsetStone()メソッドにクリックされたCellの座標が渡されます。

store側では、Cellのplaceableプロパティがtrueでない場合は、そこには石を配置出来ないので何もせずにreturnしています。

そうでない場合は、クリックされた場所およびそこを起点として裏返し対象になるCellのcolorプロパティを全てプレーヤーの石と同じ色に変更します。

「裏返し対象のCell」というのは、あらかじめ上の「次に石を配置可能な場所」の判定処理の中で見つかったCellの座標一覧をcell.turnableCellsというプロパティに格納してあるので、それをループすることで取得できます。

裏返し対象のCellのcolorプロパティを全て変更する処理が終わった後、getNextTurn()メソッドを呼んで次のプレーヤーに手番を交替しています。

最後に'board_changed'イベントを発行して、各コンポーネントの描画を更新しています。



3. 石を置ける場所が無い場合の「パス」処理


現在の手番で石を置ける場所が見つからない場合は、ボードの placeableCount プロパティが 0 になります。

その場合は、ひとまず alert()でメッセージを表示した後、storeのskipTurn()メソッドを呼んで手番を「パス」します。

この処理はゲームの進行に関わる処理なので、Gameコンポーネント内で行っています。

skipTurn()メソッドの中では、ボードの状態を変更せずに、単に現在の手番を示す turn プロパティを更新して'board_changed'イベントを発行しています。



4. ゲーム終了判定と勝敗の表示


Gameコンポーネントでstoreから'board_changed'イベントを受け取った時に、finishedプロパティがtrueであればゲーム終了なのでその旨をalert()で表示します。

finishedプロパティがtrueになる条件は、以下の通りです。

  • 空白のCellが無くなった。
  • 黒または白のどちらかが全滅した。
  • 両者とも置く場所が無くなった。


ボードの状態を更新する際にこのいずれかに当てはまるかどうかを毎回チェックしてfinishedプロパティの値をセットしています。



以上でリバーシのルールを実装して実際に遊べる状態にまで持って行くことが出来ました。

次回は、コンピュータの思考ルーチンを作成してコンピュータとの対戦機能を追加したいと思います!


今回の時点でのソースコードはこちら:

https://github.com/mikehibm/reversi-react/tree/blog-2






 

2018年10月20日

React/TypeScriptでリバーシゲームを作る (1) - ボードの描画と石の配置

実は2011年に「Androidでオセロゲームを作る」というのをやった事があります。5回のブログ記事に分けて少しずつ作り込んで行ったのですが、このシリーズが今でも結構アクセスされているみたいです。

これを読み返しながら、ふと「今だったら、ReactでWeb版を作っておけばネイティブアプリ化も結構簡単に出来るし色々と応用が効くのでは?」と思い付きました。

そこで、作り始めて見ました。

現時点で動作しているものはこちらで試せます。
https://reversi-mngzdihdyn.now.sh/

タイトル画面


ゲーム画面

ソースコードはこちら

https://github.com/mikehibm/reversi-react/tree/blog-1


まだ画面をクリックして石を置くことが出来るようになっただけでゲームのルールが何も実装されていません。

しかし、リバーシゲーム作りは楽しいですね!


以下、ここまでに書いたコードの大まかな説明です。

コンポーネント階層


今のところ下のような階層になっています。

  • App (アプリケーションの大枠)
    • Menu (メニュー画面)
    • Game (ゲーム画面)
      • Board (ボードの背景や枠線などを描画するコンポーネント)
        • Cell (一つのセルを描画するコンポーネント)



状態管理


アプリケーションの状態は store.ts ファイルで一括管理しています。

ここでエクスポートしている Store クラスが EventEmitter を継承しており、状態に変更があったときにイベントを発生(emit)することでそれを各コンポーネントに通知して、各コンポーネントが画面を更新するという流れになっています。

例えば、メニュー画面でスタートボタンが押されたときには、setPage()メソッドが呼ばれて Store クラス内で保持している page 変数の値が変更されるので、その際に 'page_changed' というイベントを発生させています。

このイベントは App コンポーネントが監視していて、イベント発生時に最新の page の値を受け取って メニュー画面を表示するのかゲーム画面を表示するのかを切り替えています。





ボードとセルの描画


Boardコンポーネントでは、SVGを使ってボードの緑色の背景や縦と横の罫線などを描画しています。

また重要なのは、Cellコンポーネントの配列を作成してそれを子コンポーネントとして描画していることです。ボード上には 8 x 8 で64個のCellコンポーネントが配置されることになります。

Cellコンポーネントでは、propsとして渡された座標値にしたがってSVGのRectangleを描画します。このRectangleがマウスのクリックイベントを受け取る入り口になります。

また、propsのひとつとしてcolorが渡されますが、これはそのセルが空白なのか、黒の石が配置されているのか、または白の石が配置されているのか、を示します。

Cellコンポーネントではその値によって黒または白の円を描画する処理を行っています。

あるセル上でマウスがクリックされた時は、次の流れで最終的にセルに石が描画されるようになっています。


  1. handleClick()メソッドが呼ばれる。
  2. Storeクラスの setStone()メソッドが呼ばれる。
  3. クリックされた場所に石が無ければ、その場所の colorプロパティを現在の手番の色に変更する。
  4. Storeクラスが'board_changed'イベントを発生する。
  5. Boardコンポーネントがイベントを受け取り、ボード全体を再描画する。
  6. クリックされた場所に対応するCellコンポーネントが石を描画する。


こんな感じで、ひとまずボードの描画と石の配置までは出来るようになりました。

次回は、リバーシゲームとしてのルールを実装して実際に遊べるようになるまでを作ってみたいと思います!








2018年8月10日

ReactでCSVファイルを読み込んでクライアント側で帳票を出力する

やりたかった事


あるWebサービスからCSV形式でデータをエクスポート出来るのですが、そのデータからちょっとした帳票を印刷したいという要望がありました。

そこでなるべく手っ取り早く、ブラウザだけで動くものが出来ないかと考えて作ってみたのが今回のプログラムです。

大まかな流れは、
  1. CSVファイルを指定すると、ブラウザ上のJavaScriptでその内容を読み込む。
  2. 読み込んだ内容からHTMLで帳票を生成して表示する。
  3. ブラウザの印刷機能を使って手動で印刷またはPDFとして保存する。
という感じになります。


CSVファイルの例







作成したい帳票


実際に動いているもの

https://mikehibm.github.io/react-csv-example/




1. プロジェクトを作成する


プロジェクトは前回のエントリーと同様に Create React App + TypeScriptで作ることにします。
npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start

FileSelect.tsx と Report.tsx というファイルを新規作成して、App.tsx からこれらを呼び出して使うようにします。

最初は FileSelect コンポーネントを表示してCSVファイルの選択を行い、ボタンがクリックされたらファイルの内容を読み取って、Report コンポーネントに渡します。




Report コンポーネントが表示されている時は FileSelect コンポーネントは非表示になるようにします。




2. JavaScriptでCSVファイルの内容を読み込む


HTML5のFile APIを使います。FileReaderでの読み込みは非同期処理なので、処理をラップしてPromiseを返す関数を作りました。







3. ファイルの内容をパースしてオブジェクトの配列に変換する


上の readFileAsText() からファイル全体の内容が文字列として返ってくるので、それをパースしてオブジェクトの配列に変換します。

具体的には、まず改行文字で区切って行単位の配列に分け、さらにその各行について「,」で区切って列単位の配列に分けます。つまり最終的には string[][] (文字列の配列の配列)になります。




ここでは簡単に改行文字(\n)と「,」で区切っているだけですが、実際には改行文字が違う、値に「,」が含まれている、などさまざまなケースがあり得るのでCSVのパース処理はちゃんとやろうとすると実は結構大変です。

なので必要であれば下の記事で紹介されている csv-parser などを使った方が良いかもしれません。

ブラウザ上でCSVファイルをパースする
https://qiita.com/ledsun/items/e38ee0dff8f26bf8d930




その後、文字列の配列(の配列)から、今度は帳票を生成する時に扱いやすいように、WorkItemというクラスのインスタンスの配列に変換します。

この時に、文字列型から日付型への変換や、CSVファイルに無い項目の値(duration)の計算なども同時に行っています。





4. HTMLで帳票を生成する


WorkItemオブジェクトの配列を受け取って、tableタグで表形式のHTMLに変換します。

本当は帳票のタイトル、見出し、明細行などをそれぞれ別のコンポーネントに分けた方が良いのだと思いますが、今回はあまり時間がなかったので1コンポーネントで作ってしまいました。





あとは画面に表示された帳票をブラウザの印刷機能で印刷するだけです。デフォルトではおそらく余計なヘッダーやフッター(ページタイトルやURL)も印刷されてしまいますが、印刷時の設定画面でこれらをオフにしておけばきれいに出力されるはずです。


今回作ったアプリの全ソースコードは下記にあります。

https://github.com/mikehibm/react-csv-example







2018年8月8日

Create React App + TypeScript で最初につまずいたこと

ちょっとしたサンプルプログラム的なものを Create React App + TypeScript で作ろうと思ったのですが、思わぬところでいきなり詰まってしまいました。


Create React App(以下CRA)で TypeScript と言えば、

https://github.com/wmonk/create-react-app-typescript

が定番だと思います。


さて、下の通りにプロジェクトを作成して実行しました。

npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start


もちろんここまでは全く問題なし。早速VS Codeでプロジェクトのフォルダを開いて、アプリケーションを作り始めました。



ところが、あるタイミングでふと

console.log("hogehoge");

を入れて実行してみようと思ったところ、いきなりコンパイルエラーになってしまいました。


「Calls to 'console.log' are not allowed.」というエラーですが、これにはちょっと驚かされました。console.logを入れただけでアプリケーションが動かなくなるなんて。。。


これですが、デフォルトで設定されているTSLintのルールがめちゃくちゃ厳格なものになっているのが原因なようです。


厳格すぎてこれでは使いづらいという声も多く、GitHubのIssuesでも議論になっているようですね。


オリジナルのCRA作者のDan Abramov氏からも、「この厳しいルールはCRAを作った時の思想(初心者でもすぐに動くものを作り始められる。本当にクリティカルなもの以外はワーニングのみでエラーにはしない。)と合わないから、ゆるくした方が良いんじゃない?」 という趣旨のIssueをわざわざ上げられているぐらいです。


create-react-app-typescript リポジトリ作者のWill Monk氏によると、「最初は自分が職場で使っているルールをコピーして使っていたのだけれど、後で一般的に推奨されているプリセット("tslint:recommended", "tslint-react"など)を使うように変更した」との事。「それでもまだルールが厳しすぎるなら、誰かがオリジナルのCRAと同等のゆるいルールの設定を作ってPRしてくれたら喜んでそれをマージするよ。」とも言っているので、彼自身は特にどうしても厳しいルールを他の人に強制する気は無いようです。


その後このIssue上で議論は進んでいるようですが、このブログ執筆時点の最新版ではまだルールの変更には至っていないようです。


さて、なにはともあれとりあえず console.log を使えるようにする方法ですが、上のIssueでも言及されているのですが、Microsoftによる下記のページにわかりやすくはっきりと記載されていました。

https://github.com/Microsoft/TypeScript-React-Starter


プロジェクトのルートフォルダにある tslint.json ファイルを下のように書き換えればOKです。(赤字を削除、青字を追加。)

{
-  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
+  "extends": [],
+  "defaultSeverity": "warning",

"linterOptions": {
     "exclude": [
       "config/**/*.js",
       "node_modules/**/*.ts"
     ]
   }
 }

これで問題なく開発が続けられるようになりました!






 

2015年9月18日

React.jsの練習に◯☓ゲームを作ってみた。

ある日の深夜1時にふと思い立ってどうしても実際にReactで何か作りたくなり、朝5時までかかって大体の部分を作りました。

終了判定の部分がバグっていたりしたので2時間ほど眠ってからまた3時間ほどかけてデバッグ。翌日にまた約1時間かけて調整をして一応出来ました。

合計8時間もかかってますね。 汗)   
何せ初めてのReactなので。

クリックすると別ウィンドウでRunstant Liteが開きます。
https://goo.gl/Psx5fl

使ったツール


@phi_jp さん作成の「Runstant Lite」素晴らしいです。
ソースコードをエンコードしてURLに保存するというアイデアには脱帽しました。



初めてReact.jsで書いてみた感想


実は今AngularJSも勉強中ですが、それと比べるとReactの方が覚えないと行けない事が圧倒的に少ないので精神的に楽です。

まず親コンポーネントをとりあえず作って四角いDIVを表示する事が出来れば、そこに一つずつ子コンポーネントを付け足して行く感じでUIの作成はそれほど苦労せずに出来ました。

今回は

  • Board (盤面の背景を描画)
  • Cell  (個々のセルを描画)
  • StatusPanel (「X」の番です、などゲームの状態を表示)
  • ResultPanel (「X」の勝ち!、引き分け、などの結果を表示)

の4つのコンポーネントを作成しました。

最初は個々のCellにStateを持たせてクリック時の表示の更新をCellの内部で行っていました。でもそれだとBoard全体としての処理(終了判定など)との連携が面倒になる事に気付いて、Reactの世界の外側に作ったAppクラスで全ての状態を管理する様にしました。

ReactはあくまでView担当なので、Modelは自分で作らないとダメなんですよね。

そうなると今度は
1. Reactのコンポーネント内で発生するイベント(クリックされた、など)をModel側に伝える方法
2. 逆にModel側で発生するイベント(勝敗が着いた、など)をReactコンポーネント側に伝える方法
が必要になります。

前者の場合は単純にonClickなどのハンドラからAppクラスの関数を直接呼ぶ事も出来るのですが、後者のやり方が分からずにちょっと悩みました。

そこでFlux関係の解説を読み漁って、「とにかくPubSubで疎結合にしろ」という事なのかな〜と理解しました。

その結果、自分なりにEventEmitterもどき(?)を作って、Reactコンポーネントとモデルが必ずそれを経由して対話する様な形になりました。よくFluxで言われる「単方向のフロー」にはなっていない様な気もしますが、一応「PubSubで疎結合」にはなっているのではないかと思います。

こういうのはやっぱり自分で作って試行錯誤して見ないと腹の底から理解する事は出来ないですね。

単純な◯☓ゲームでしたがとりあえず作ってみて、Reactへの理解を少し深める事が出来たと思います。Android版のReact Nativeも出て来た事だし、これからもさらにReactに時間を投資して見ようかなと思います。


※追記 


きちんとFluxに則って作られたきれいな◯☓ゲームを見つけました。コードも見た目もきれいです。これは参考になります。

https://github.com/hackhat/tic-tac-toe-flux

ついでに、Reactで作られたゲームの一覧もありました。

36 Game Examples with ReactJS
http://react.rocks/tag/Game



React.jsの勉強のために読んだ本


入門 React ―コンポーネントベースのWebフロントエンド開発



参考になったサイト


reactjs - Reactデザインパターン - すべてがeになる - Qiita
http://qiita.com/shunjikonishi/items/ba6d981880e316131836

Flux – Dispatcher【日本語訳】と実装のポイント | mae's blog
http://mae.chab.in/archives/2738

一人React.js Advent Calendar 2014 - Qiita
http://qiita.com/advent-calendar/2014/reactjs

チュートリアル | React
https://facebook.github.io/react/docs/tutorial-ja-JP.html




.