20190302のReactに関する記事は4件です。

NERM (Node.js + Express + React + Material-UI) ベースなWebアプリ開発用のシンプルなビルド環境 2019年3月版

Node.js + Express で Web アプリを開発するのはもう一般的だと思いますが、ちょっと凝ったアプリを作成しようとすると、フロントエンドの UI で何を使おうか悩みますね。

個人的には React + Material-UI あたりが使いやすいと思います。(勝手に)名付けて NERM (Node.js + Express + React + Material-UI) 環境!で、この環境用にシンプルなビルド環境を用意してみました。

2017年に投稿した browserify+babelifyでReactアプリをトランスコンパイルできるシンプルなビルド環境を用意する が進化した2019年版です。Node.js ベースのWebアプリを気軽に作成したい場合に、スケルトンとして使っていただければ、嬉しいです。

準備

node と npm は利用できるよう準備しておいてください。私はWindows環境なので nodist を使っています。

まずは作業用のディレクトリ(フォルダ)を作成します。今回は "rtk-nerm" としました。そして src, public のサブディレクトリを作成しておきます。

シンプルな React + Material-UI サンプル

今回も非常にシンプルなサンプルを用意しましょう。

src ディレクトリに App.jsx ファイルを用意します。内容は以下をコピペしてください。

src/App.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import Button from '@material-ui/core/Button';

function App() {
  return (
    <Button variant="contained" color="primary">
      Hello World
    </Button>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('react-root')
);

また public ディレクトリに index.html ファイルを用意します。内容は以下をコピペしてください。

public/index.html
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
  <title>rtk-nerm</title>
</head>
<body>
  <div id="react-root"></div>
  <script type="text/javascript" src="./App.js"></script>
</body>
</html>

サンプルはこれで準備できましたので、いよいよ環境の構築を始めましょう。

トランスコンパイル環境の構築

作業ディレクトリに package.json ファイルを用意します。内容は以下をコピペしてください。

package.json
{
  "name": "rtk-nerm",
  "version": "1.0.0",
  "description": "Simple transcode environment for NERM (Node.js + Express + React + Material-UI) application.",
  "scripts": {
    "build": "browserify -t [ babelify --presets [ @babel/preset-env @babel/preset-react ] ] src/App.jsx -o public/App.js",
    "start": "node Server.js"
  },
  "engines": { "node": ">= 8" },
  "author": "yamachan / Toshio Yamashita",
  "license": "MIT"
}

※ scripts 要素以外は自由に変更してok

そして以下のコマンドで必要なパッケージを導入します。

npm install --save express
npm install --save react react-dom @material-ui/core @material-ui/icons
npm install --save-dev browserify babelify
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react

ちなみに私は以下のようなバージョンのモジュールが導入されました。

package.json
  "dependencies": {
    "@material-ui/core": "^3.9.2",
    "@material-ui/icons": "^3.0.2",
    "express": "^4.16.4",
    "react": "^16.8.3",
    "react-dom": "^16.8.3"
  },
  "devDependencies": {
    "@babel/core": "^7.3.4",
    "@babel/preset-env": "^7.3.4",
    "@babel/preset-react": "^7.0.0",
    "babelify": "^10.0.0",
    "browserify": "^16.2.3"
  }

以下のコマンドを実行して public/App.js ファイルが生成されれば ok です。

npm run build

Node.js + Express サーバーを起動

プロジェクトのルートディレクトリに Server.js ファイルを用意し、内容は以下をコピペしてください。

Server.js
const express = require("express");
const app = express();

app.use(express.static('public'));

let port = process.env.PORT || 3000;
app.listen(port);

以下のコマンドで Server.js コードを実行し、ローカルで Web サーバーを起動します。

npm start

ブラウザで http://localhost:3000/ にアクセスすれば、Material-UI を使った React ページが表示されます。

image.png

さあアプリ開発を開始しましょう

Server.js にはバックエンドのロジックを実装しましょう。こちらを更新した時には npm start で起動するWeb サーバーを再起動(終了して再実行)することを忘れずに。

src/App.jsx にはフロントエンドの UI を実装し、npm run build で public/App.js を更新しましょう。

それぞれ拡張し、自分なりの Web アプリケーションを作成してみてください。

IBM Cloud の Node.js 環境で動かしてみる

ついでにプロジェクトをIBM Cloud (Cloud Foundry) の Node.js 環境で動かしてみましょう。

IBM Cloudアカウントをお持ちでない方は、こちらからすぐに使える無料アカウント登録をしてください。

プロジェクトのルートディレクトリに .cfignore ファイルを用意し、内容は以下をコピペしてください。このファイルが無くても動作しますが、用意したほうが push 動作が軽くなります。

.cfignore
node_modules
src
README.md
.gitignore

さあ、IBM Cloud にログインして、アプリを push しましょう。(アプリ名 rtk-nerm はユニークなものに変更する必要があります)

アプリ名はWebサイトを公開した時のURLの一部になります。IBM Cloudの場合、https://<アプリ名>.mybluemix.netとなります。

cf login
cf push -m 128M rtk-nerm

【追記】package.json に start コマンドを記載したので -c "node Server.js" オプションは不要になりました

IBM Cloud ダッシュボードでアプリの開始を確認します。
image.png
「アプリURLにアクセス」リンクから、実際の表示を確認します。
image.png

関連ファイルのダウンロード

rtk-nerm というGitHubリポジトリに今回のファイルを置いておきましたので、面倒な方はそちらからダウンロード/clone してお使いください。

Enjoy!

Node.js でロジックを実装し、Express + React + Material-UI ベースで開発したWebアプリをビルドするための、できるだけシンプルな環境を構築してみました。これを出発点として自分なりのアプリやビルド環境を構築する助けになれば嬉しいです。

ではまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】react-templatesについてまとめた

react-templates

install

https://www.npmjs.com/package/react-templates

npm i -g react-templates

gulp: https://www.npmjs.com/package/gulp-react-templates

npm i gulp-react-templates

gulp task code

サンプル

const gulp = require('gulp');
const rt = require('gulp-react-templates');
const rename = require("gulp-rename"); // 出力ファイル名を変更するため

const rt_path = './src/rt/*.rt';

gulp.task('rt', () => {
  return gulp.src(rt_path)
    .pipe(rt({ modules: 'es6' })) // 吐き出す際の出力フォーマットを選択
    .pipe(rename(name_info => {
      const new_basename = name_info.basename.replace('.rt', ''); // '.rt'を''に置換
      name_info.basename = new_basename;
    }))
    .pipe(gulp.dest('./src/templates'));
});

gulp.task("watch", () => {
  return gulp.watch(rt_path, gulp.series(['rt']));
});

Command line Interface

source: https://github.com/wix/react-templates/blob/gh-pages/docs/cli.md

先ほどの出力する際のoptionの例を以下に記載

// temp.rt
<h2>Hello React</h2>
// es6
// rt [file.rt|glob]* -m es6
import * as React from 'react';
import * as _ from 'lodash';
export default function () {
    return React.createElement('h2', {}, 'Hello React');
}
// commonJS
// rt [file.rt|glob]* -m commonJS
'use strict';
var React = require('react');
var _ = require('lodash');
module.exports = function () {
    return React.createElement('h2', {}, 'Hello React');
};
// none (default)
// rt [file.rt|glob]*
var tempRT = function () {
    return React.createElement('h2', {}, 'Hello React');
};

reactに組み込む

▼ こんなrtファイルを書いて

// temp.rt
<h2>Hello react-templates</h2>

▼ コンパイル(es6)してこんな感じにして

// temp.js
import * as React from 'react';
import * as _ from 'lodash';
export default function () {
    return React.createElement('h2', {}, 'Hello react-templates');
}

▼ importしてrenderのなかでreturnする

// App.js
import React, { Component } from 'react';
import Temp from './templates/temp.js';

class App extends Component {
  render() {
    return Temp();
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

ブラウザに Hello react-templates が出力されればok

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

takeWhile はメモリリーク (無駄なメモリ消費) の原因になり得る

takeWhile に参照透過でない式を渡すのは良くない。

上記が分かっていれば下記を読む必要はありません。

経緯

React開発ノウハウメモ(随時更新)
上記記事を読んで unsubscribecomponentWillUnmount に書かない方法を知ったので調べてみた。1

takeWhile はメモリリークの原因になり得る。

takeWhile は値が来たときのみ実行されるので値が来なければ unsubscribe されない。
無駄なネットワーク通信やDBへのアクセスを続けてしまう。
takeWhile は値に応じて処理を続けるべきか決まる場合で使うべき。

Observable を流れる値と関係なく処理を止めたいなら unsubscribe を呼ぶか takeUntil を使う。

テストしてみる

実際に試してみるためのコードを書いた。
それらで下記の 3 つの関数を使っている。

utils.ts
// s 秒待つ
const sleep = (s: number) => new Promise(r => setTimeout(r, s * 1000))
// タイムスタンプをつけてログを出す
const log = (...v: any[]) => {
    const ts = (performance.now() / 1000) | 0
    console.log(...v, `(${ts})`)
}
// キャンセルできる Observable を生成
const make = (name: string) => {
    return new Observable<number>(s => {
        let canceled = false
        const push = (v: number) => {
            log(name, 'sub1', v, '- canceled', canceled)
            s.next(v)
        }
        Promise.resolve().then(async () => {
            push(12) // すぐに 12 を渡す
            await sleep(10) // 10 秒待ってから
            push(13) // 13 を渡す
            push(14) // 14 を渡す
        })
        return () => {
            log(name, 'unsub1')
            canceled = true
        }
    })
}

テスト内容

上記 makeObservable を生成したのち 1 秒後に処理を止める。

unsubscribe する

一般的なやりかたであると思われる。
問題なく動いている。

test1.ts
const test1 = async () => {
    const read = make('test1').pipe(
        tap(v => log('test1', 'sub2', v)),
    ).subscribe()
    await sleep(1)
    read.unsubscribe()
}
test1() /*
test1 sub1 12 - canceled false (0)
test1 sub2 12 (0)
test1 unsub1 (1)
test1 sub1 13 - canceled true (10)
test1 sub1 14 - canceled true (10)
*/

takeWhile を使う

1 秒後に止めようとしたが、その後 9 秒経って次の値が来るまで止まっていない。

test2.ts
const test2 = async () => {
    let alive = true
    make('test2').pipe(
        takeWhile(() => alive),
        tap(v => log('test2', 'sub2', v)),
    ).subscribe()
    await sleep(1)
    alive = false
}
test2() /*
test2 sub1 12 - canceled false (0)
test2 sub2 12 (0)
test2 sub1 13 - canceled false (10)
test2 unsub1 (10)
test2 sub1 14 - canceled true (10)
*/

takeUntil を使う

問題なく動く。

test3.ts
const test3 = async () => {
    const unsub = new Subject<void>()
    make('test3').pipe(
        takeUntil(unsub),
        tap(v => log('test3', 'sub2', v)),
    ).subscribe()
    await sleep(1)
    unsub.next()
    unsub.complete()
}
test3() /*
test3 sub1 12 - canceled false (0)
test3 sub2 12 (0)
test3 unsub1 (1)
test3 sub1 13 - canceled true (10)
test3 sub1 14 - canceled true (10)
*/

unsubscribetakeUntil 、どちらを使うべきか?

こちらの記事 (英語)では takeUntil がオススメされている。

手続き的な unsubscribe よりも宣言的な takeUntil が良いという判断だろうか?

補足

ReactRxJS を組み合わせる場合

大規模なアプリなら redux-observable を、小規模なら rxjs-hooks を使うことを勧める。
古い React を使っているなら recompose も良い。


  1. useEffect のある今の ReactcomponentWillUnmount のような古い方法を使う必要はないと思う。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-typescript-wijmoでサンプル

注意書き

C●deZineで見かけたサンプルを参考に実装しています。
(そっくりそのままtsに移植しただけですごめんなさい)

作成したコードは以下にあります。
https://github.com/ishibashi-futos/react-wijmo

当方環境

package.json
{
  "name": "wijmo-ts",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.8.3",
    "react-dom": "^16.8.3",
    "react-scripts-ts": "3.1.0",
    "wijmo": "^5.20183.568"
  },
  "scripts": {
    "start": "react-scripts-ts start",
    "build": "react-scripts-ts build",
    "test": "react-scripts-ts test --env=jsdom",
    "eject": "react-scripts-ts eject"
  },
  "devDependencies": {
    "@types/jest": "^24.0.6",
    "@types/node": "^11.9.5",
    "@types/react": "^16.8.4",
    "@types/react-dom": "^16.8.2",
    "typescript": "^3.3.3333"
  }
}

create-react-appしてサーバを立ち上げる

create-react-appコマンドを利用して、reactアプリの雛形を出します。
今回は typescript を利用するので、 --scripts-version オプションをつけます。

$ create-react-app wijmo-ts  --scripts-version=react-scripts-ts
$ cd wijmo-ts/ && yarn start # or npm start

よく見るReactの画面が出てきたかと思います(スクショ略)
このあと、 npm install --save wijmo とすると、↑のような package.json ができるかと思います。

guageサンプルを実行してみる。

完成品はこちら(いきなり)

Guage.tsx
import * as React from 'react';
import 'wijmo/styles/wijmo.css';
import * as wjGauge from 'wijmo/wijmo.react.gauge';

interface IState {                                          // point 1
  gaugeValue: number;
}

class Guage extends React.Component<{}, IState> {

  constructor(props: any) {
    super(props);
    this.state = {
      gaugeValue: 30
    }
    this.gaugeChanged = this.gaugeChanged.bind(this);       // point 2
    this.textboxChanged = this.textboxChanged.bind(this);
  }

  public render() {
    return (
      <div className="App">
        <h1>Wijmo + React(ゲージ)</h1>
        <wjGauge.LinearGauge className="wijmo-control"
        value={this.state.gaugeValue} valueChanged={this.textboxChanged}/>
        gaugeValue:<input type="text" className="textBox" 
        value={this.state.gaugeValue} onChange={this.textboxChanged}/>
      </div>
    );
  }

  private gaugeChanged(newValue: number) {
    this.setState({gaugeValue: newValue});
  }

  private textboxChanged(newValue: React.ChangeEvent<HTMLInputElement>) {
    const input = newValue.target as HTMLInputElement;
    if (input != null) {                                    // point 3
      if (!isNaN(Number(input.value))) {
        this.setState({gaugeValue: Number(input.value)});
      }
    }
  }

}

export default Guage;

react自体触るのがほとんど初めてだったこともあり、wijmoと関係ないところではまりました。

point 1. interface name must start with a capitalized I

TSLintのデフォルト設定なのか、InterfaceのprefixにIを指定してないと、npm startしたときにエラーになります・・・。
tslint.jsonの rulesに以下のように設定すると消えます。

tslint.json
{
  "rules" : {
    "interface-name": [false]
  }
}

point 2. 'gaugeChanged' is declared but its value is never read.

リンク先でも触れている通り、React.Componentを使用する場合は、
イベントハンドラが自動的にbindされないらしいので、コンストラクタ内でbindする。

point3. TypeError: Cannot read property 'value' of undefined

イベントが発生した時に、HTMLInputElementが取得できていない時がある。
そのため上記のようなエラーが出るので、HTMLInputElementの型で取れなかった(nullだった?)時は
処理しないように直してみた。

終了

長くなってきたので、Gridのサンプルは別に書きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む