ReactとNashornを使用した同形アプリケーション
1. 概要
このチュートリアルでは、同形アプリとは正確に何であるかを理解します。 また、JavaにバンドルされているJavaスクリプトエンジンであるNashornについても説明します。
さらに、Nashornを React などのフロントエンドライブラリと一緒に使用して、同形のアプリを作成する方法についても説明します。
2. 少しの歴史
従来、クライアントおよびサーバーアプリケーションは、サーバー側で非常に重い方法で作成されていました。 PHPは、ほとんど静的なHTMLとそれらをレンダリングするWebブラウザーを生成するスクリプトエンジンと考えてください。
Netscapeは、90年代半ばにブラウザでJavaScriptをサポートするようになりました。 これにより、処理の一部がサーバー側からクライアント側のブラウザーに移行し始めました。 長い間、開発者はWebブラウザでのJavaScriptサポートに関するさまざまな問題に苦労していました。
より高速でインタラクティブなユーザーエクスペリエンスへの需要が高まるにつれ、境界はすでに厳しくなりました。 ゲームを変更した最も初期のフレームワークの1つはjQueryでした。それはいくつかのユーザーフレンドリーな機能とAJAXの大幅に強化されたサポートをもたらしました。
すぐに、フロントエンド開発用の多くのフレームワークが登場し始め、開発者のエクスペリエンスが大幅に向上しました。 GoogleのAngularJS、FacebookのReact、そしてその後 Vue、から始まり、開発者の注目を集め始めました。
最新のブラウザサポート、優れたフレームワーク、および必要なツールにより、潮流は主にクライアント側にシフトしています。
ますます高速化するハンドヘルドデバイスでの没入型エクスペリエンスには、より多くのクライアント側処理が必要です。
3. 同形アプリとは何ですか?
そのため、フロントエンドフレームワークが、ユーザーインターフェイスがクライアント側で完全にレンダリングされるWebアプリケーションの開発にどのように役立っているかを確認しました。
ただし、サーバー側で同じフレームワークを使用して、同じユーザーインターフェイスを生成することも可能です。
今では、必ずしもクライアント側のみまたはサーバー側のみのソリューションに固執する必要はありません。 より良い方法は、クライアントとサーバーの両方が同じフロントエンドコードを処理し、同じユーザーインターフェイスを生成できるソリューションを用意することです。
このアプローチには利点がありますが、これについては後で説明します。
このようなWebアプリケーションは、IsomorphicまたはUniversalと呼ばれます。 現在、クライアント側の言語はほとんどがJavaScriptです。 したがって、同形アプリが機能するには、サーバー側でもJavaScriptを使用する必要があります。
Node.jsは、サーバー側でレンダリングされたアプリケーションを構築するための最も一般的な選択肢です。
4. ナソーンとは?
では、Nashornはどこに収まり、なぜそれを使用する必要があるのでしょうか。 Nashornは、デフォルトでJavaにパッケージ化されたJavaScriptエンジンです。 したがって、すでにJavaでWebアプリケーションのバックエンドがあり、同形のアプリを構築したい場合は、Nashornが非常に便利です。
NashornはJava8の一部としてリリースされました。 これは主に、Javaでの埋め込みJavaScriptアプリケーションの許可に焦点を当てています。
Nashorn は、メモリ内のJavaScriptをJavaバイトコードにコンパイルし、実行のためにJVMに渡します。 これにより、以前のエンジンであるRhinoと比較してパフォーマンスが向上します。
5. 同形アプリの作成
これで、十分なコンテキストを通過しました。 ここでのアプリケーションは、フィボナッチ数列を表示し、シーケンス内の次の番号を生成して表示するためのボタンを提供します。 バックエンドとフロントエンドを使用して、単純な同形アプリを作成しましょう。
- フロントエンド:シンプルなReact.jsベースのフロントエンド
- バックエンド:JavaScriptを処理するためのNashornを備えたシンプルなSpringBootバックエンド
6. アプリケーションフロントエンド
フロントエンドの作成にはReact.jsを使用します。 Reactは、シングルページアプリを構築するための人気のあるJavaScriptライブラリです。 は、複雑なユーザーインターフェイスを階層コンポーネントに分解し、オプションの状態と一方向のデータバインディングを使用するのに役立ちます。
Reactはこの階層を解析し、仮想DOMと呼ばれるメモリ内データ構造を作成します。 これは、Reactが異なる状態間の変更を見つけ、ブラウザーのDOMに最小限の変更を加えるのに役立ちます。
6.1. Reactコンポーネント
最初のReactコンポーネントを作成しましょう:
var App = React.createClass({displayName: "App",
handleSubmit: function() {
var last = this.state.data[this.state.data.length-1];
var secondLast = this.state.data[this.state.data.length-2];
$.ajax({
url: '/next/'+last+'/'+secondLast,
dataType: 'text',
success: function(msg) {
var series = this.state.data;
series.push(msg);
this.setState({data: series});
}.bind(this),
error: function(xhr, status, err) {
console.error('/next', status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.setState({data: this.props.data});
},
getInitialState: function() {
return {data: []};
},
render: function() {
return (
React.createElement("div", {className: "app"},
React.createElement("h2", null, "Fibonacci Generator"),
React.createElement("h2", null, this.state.data.toString()),
React.createElement("input", {type: "submit", value: "Next", onClick: this.handleSubmit})
)
);
}
});
ここで、上記のコードが何をしているのかを理解しましょう。
- まず、Reactで「App」と呼ばれるクラスコンポーネントを定義しました
- このコンポーネント内で最も重要な機能は、ユーザーインターフェイスの生成を担当する「render」です。
- コンポーネントが使用できるスタイルclassNameを提供しました
- ここでは、コンポーネントの状態を利用して、シリーズを保存および表示しています。
- 状態は空のリストとして初期化されますが、コンポーネントがマウントされるときに、コンポーネントに渡されたデータを小道具としてフェッチします
- 最後に、「追加」ボタンをクリックすると、RESTサービスへのjQuery呼び出しが行われます。
- 呼び出しは、シーケンス内の次の番号をフェッチし、それをコンポーネントの状態に追加します
- コンポーネントの状態を変更すると、コンポーネントが自動的に再レンダリングされます
6.2. Reactコンポーネントの使用
Reactは、HTMLページで名前付きの「div」要素を探してそのコンテンツを固定します。 この「div」要素を含むHTMLページを提供し、JSファイルをロードするだけです。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello React</title>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
ReactDOM.render(
React.createElement(App, {data: [0,1,1]}),
document.getElementById("root")
);
</script>
</body>
</html>
それで、ここで何をしたか見てみましょう:
- 必要なJSライブラリ、react、react-dom、jQueryをインポートしました
- その後、「root」と呼ばれる「div」要素を定義しました
- また、Reactコンポーネントを使用してJSファイルをインポートしました
- 次に、いくつかのシードデータ(最初の3つのフィボナッチ数)を使用してReactコンポーネントを「アプリ」と呼びました。
7. アプリケーションバックエンド
それでは、アプリケーションに適したバックエンドを作成する方法を見てみましょう。 このアプリケーションを構築するために、をSpringWebと一緒にSpring Boot使用することをすでに決定しています。 さらに重要なことに、前のセクションで開発したJavaScriptベースのフロントエンドを使用して処理することにしました。
7.1. Mavenの依存関係
単純なアプリケーションでは、JSPをSpring MVCと一緒に使用するため、POMにいくつかの依存関係を追加します。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
1つ目は、Webアプリケーションの標準のスプリングブート依存関係です。 2つ目は、JSPのコンパイルに必要です。
7.2. Webコントローラー
次に、JavaScriptファイルを処理し、JSPを使用してHTMLを返すWebコントローラーを作成しましょう。
@Controller
public class MyWebController {
@RequestMapping("/")
public String index(Map<String, Object> model) throws Exception {
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
nashorn.eval(new FileReader("static/js/react.js"));
nashorn.eval(new FileReader("static/js/react-dom-server.js"));
nashorn.eval(new FileReader("static/app.js"));
Object html = nashorn.eval(
"ReactDOMServer.renderToString(" +
"React.createElement(App, {data: [0,1,1]})" +
");");
model.put("content", String.valueOf(html));
return "index";
}
}
だから、ここで正確に何が起こっているのですか?
- ScriptEngineManagerからタイプNashornのScriptEngineのインスタンスをフェッチします。
- 次に、関連するライブラリを React、react.js、react-dom-server.jsにロードします。
- また、reactコンポーネント「App」を含むJSファイルをロードします
- 最後に、コンポーネント「App」といくつかのシードデータを使用して反応要素を作成するJSフラグメントを評価します
- これにより、ObjectとしてHTMLフラグメントであるReactの出力が提供されます。
- このHTMLフラグメントをデータとして関連するビュー(JSP)に渡します
7.3. JSP
では、JSPでこのHTMLフラグメントをどのように処理しますか?
Reactは、その出力を名前付きの「div」要素(この場合は「root」)に自動的に追加することを思い出してください。 ただし、サーバー側で生成されたHTMLフラグメントをJSPの同じ要素に手動で追加します。
今すぐJSPがどのように見えるか見てみましょう。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello React!</title>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="root">${content}</div>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
ReactDOM.render(
React.createElement(App, {data: [0,1,1]}),
document.getElementById("root")
);
</script>
</body>
</html>
これは、以前に空だった「ルート」divにHTMLフラグメントを追加したことを除いて、以前に作成したページと同じです。
7.4. RESTコントローラー
最後に、シーケンス内の次のフィボナッチ数を提供するサーバー側のRESTエンドポイントも必要です。
@RestController
public class MyRestController {
@RequestMapping("/next/{last}/{secondLast}")
public int index(
@PathVariable("last") int last,
@PathVariable("secondLast") int secondLast) throws Exception {
return last + secondLast;
}
}
ここでは特別なことは何もありません。単純なSpringRESTコントローラーだけです。
8. アプリケーションの実行
これで、フロントエンドとバックエンドが完了したので、アプリケーションを実行します。
ブートストラップクラスを利用して、SpringBootアプリケーションを通常どおりに起動する必要があります。
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
このクラスを実行すると、 Spring BootがJSPをコンパイルし、残りのWebアプリケーションとともに組み込みのTomcatで使用できるようにします。
今、私たちのサイトにアクセスすると、次のように表示されます。
イベントのシーケンスを理解しましょう:
- ブラウザがこのページをリクエストします
- このページのリクエストが届くと、SpringWebコントローラーがJSファイルを処理します
- NashornエンジンはHTMLフラグメントを生成し、これをJSPに渡します
- JSPは、このHTMLフラグメントを「root」div要素に追加し、最終的に上記のHTMLページを返します。
- ブラウザはHTMLをレンダリングし、その間にJSファイルのダウンロードを開始します
- 最後に、ページはクライアント側のアクションの準備ができています—シリーズにさらに番号を追加できます
ここで理解しておくべき重要なことは、Reactがターゲットの「div」要素でHTMLフラグメントを見つけた場合に何が起こるかということです。 このような場合、 Reactはこのフラグメントを持っているものと比較し、判読可能なフラグメントが見つかった場合は置き換えません。 これはまさにサーバーサイドレンダリングと同形アプリを強化するものです。
9. これ以上何が可能ですか?
簡単な例では、可能なことの表面をかじったところです。 最新のJSベースのフレームワークを備えたフロントエンドアプリケーションは、ますます強力で複雑になっています。 この複雑さが増すにつれ、私たちが注意しなければならないことがたくさんあります。
- アプリケーションで作成したReactコンポーネントは1つだけですが、実際には、これは階層を形成する複数のコンポーネントであり、データを小道具に渡します。
- コンポーネントごとに個別のJSファイルを作成して、コンポーネントを管理しやすくし、「エクスポート/要求」または「エクスポート/インポート」を通じて依存関係を管理したいと考えています。
- さらに、コンポーネント内でのみ状態を管理できない場合があります。 Reduxのような状態管理ライブラリを使用したい場合があります
- さらに、アクションの副作用として外部サービスと対話する必要がある場合があります。 これには、redux-thunkやRedux-Sagaのようなパターンを使用する必要がある場合があります
- 最も重要なことは、ユーザーインターフェイスを記述するためにJSの構文拡張であるJSXを活用したいということです。
Nashornは純粋なJSと完全に互換性がありますが、上記のすべての機能をサポートしているとは限りません。 これらの多くは、JSとの互換性があるため、トランスコンパイルとポリフィルが必要です。
このような場合の通常の方法は、WebpackやRollupなどのモジュールバンドラーを活用することです。 彼らが主に行うことは、すべてのReactソースファイルを処理し、それらをすべての依存関係とともに単一のJSファイルにバンドルすることです。 これには、JavaScriptを下位互換性があるようにコンパイルするために、Babelのような最新のJavaScriptコンパイラが常に必要です。
最終的なバンドルには古き良きJSしか含まれていません。これはブラウザが理解でき、Nashornも準拠しています。
10. 同形アプリの利点
そのため、同形アプリについて多くのことを話し、今では単純なアプリケーションを作成しました。 しかし、なぜ私たちはこれを気にする必要があるのでしょうか? 同形アプリを使用する主な利点のいくつかを理解しましょう。
10.1. 最初のページのレンダリング
同形アプリの最も重要な利点の1つは、最初のページのレンダリングが高速になることです。 典型的なクライアント側のレンダリングされたアプリケーションでは、ブラウザはすべてのJSおよびCSSアーティファクトをダウンロードすることから始めます。
その後、最初のページを読み込んでレンダリングを開始します。 サーバー側からレンダリングされた最初のページを送信すると、これははるかに高速になり、ユーザーエクスペリエンスが向上します。
10.2. SEOフレンドリー
サーバーサイドレンダリングでよく引用されるもう1つのの利点は、SEOに関連しています。 検索ボットはJavaScriptを処理できないため、Reactなどのライブラリを介してクライアント側でレンダリングされたインデックスページは表示されないと考えられています。 したがって、サーバー側でレンダリングされたページはSEOに適しています。 ただし、最近の検索エンジンボットがJavaScriptを処理すると主張していることは注目に値します。
11. 結論
このチュートリアルでは、同形アプリケーションとNashornJavaScriptエンジンの基本的な概念について説明しました。 さらに、Spring Boot、React、およびNashornを使用して同形アプリを構築する方法を検討しました。
次に、フロントエンドアプリケーションを拡張する他の可能性と、同形アプリを使用する利点について説明しました。
いつものように、コードはGitHubのにあります。