React.jsエコシステムのナビゲート
公開: 2022-03-11JavaScript Landのイノベーションの速度は非常に速いため、逆効果だと考える人もいます。 図書館は、アーリーアダプターのおもちゃから最先端のおもちゃ、そして数か月の間に退行するまで行くことができます。 少なくとももう1年間は関連性を維持するツールを特定できるようになることは、それ自体が芸術になりつつあります。
React.jsが2年前にリリースされたとき、私はAngularを学んでいたところだったので、Reactをあいまいでまだ別のテンプレートライブラリとしてすぐに却下しました。 この2年間で、AngularはJavaScript開発者の間で実際に足場を築き、現代のJS開発の代名詞になりました。 私は非常に保守的な企業環境でAngularを見始めましたが、それは当然のことながら明るい未来だと思っていました。
しかし、突然、奇妙なことが起こりました。 Angularはオズボーン効果、つまり「事前発表による死」の犠牲になったようです。 チームは、最初に、Angular 2は完全に異なり、Angular 1からの明確な移行パスがないこと、そして次に、Angular2はもう1年ほど利用できないことを発表しました。 それは、新しいWebプロジェクトを開始したい人に何を伝えますか? 新しいバージョンのリリースによって廃止される予定のフレームワークで新しいプロジェクトを作成しますか?
開発者の間のこの不安は、コミュニティでの地位を確立しようとしていたReactの手に渡りました。 しかし、Reactは常に「MVC」の「V」として売り出されていました。 これは、完全なフレームワークでの作業に慣れているWeb開発者の間である程度のフラストレーションを引き起こしました。 不足している部分を埋めるにはどうすればよいですか? 自分で書くべきですか? 既存のライブラリを使用する必要がありますか? もしそうなら、どれですか?
案の定、Facebook(React.jsの作成者)はもう1つのエースを持っていました。それは、不足している「M」と「C」の機能を埋めることを約束したFluxワークフローです。 さらに興味深いことに、Facebookは、Fluxはフレームワークではなく「パターン」であり、Fluxの実装はパターンの一例にすぎないと述べました。 彼らの言葉通り、彼らの実装は本当に単純であり、物事を進めるために多くの冗長で反復的な定型文を書く必要がありました。
オープンソースコミュニティが助けになり、1年後、数十のFluxライブラリがあり、それらを比較することを目的としたいくつかのメタプロジェクトさえあります。 これは良いことです。 Facebookは、既成の企業フレームワークをリリースする代わりに、コミュニティへの関心をかき立て、人々に独自のソリューションを考え出すように促しました。
このアプローチには興味深い副作用が1つあります。完全なフレームワークを取得するために多くの異なるライブラリを組み合わせる必要がある場合、ベンダーロックインを効果的に回避し、独自のフレームワーク構築中に出現するイノベーションを簡単に再利用できます。他の場所。
そのため、Reactの周りの新しいものはとても興味深いものです。 そのほとんどは、他のJavaScript環境で簡単に再利用できます。 Reactを使用する予定がない場合でも、Reactのエコシステムを確認することは刺激的です。 強力でありながら比較的簡単に構成できるモジュールバンドラーWebpackを使用してビルドシステムを簡素化するか、Babelコンパイラーを使用してECMAScript6およびECMAScript7の作成を開始することをお勧めします。
この記事では、利用可能な興味深い機能とライブラリのいくつかについて説明します。 それでは、Reactエコシステムを探索しましょう!
ビルドシステム
ビルドシステムは、間違いなく、新しいWebアプリケーションを作成するときに最初に気にする必要があることです。 ビルドシステムは、スクリプトを実行するためのツールであるだけでなく、JavaScriptの世界では、通常、アプリケーションの一般的な構造を形成します。 ビルドシステムがカバーしなければならない最も重要なタスクは次のとおりです。
- 外部および内部の依存関係の管理
- コンパイラとプリプロセッサの実行
- 生産のための資産の最適化
- 開発Webサーバー、ファイルウォッチャー、およびブラウザーリローダーの実行
近年、BowerとGruntを使用したYeomanワークフローは、現代のフロントエンド開発の聖なる三位一体として提示され、ボイラープレート生成、パッケージ管理、および一般的なタスクの実行の問題をそれぞれ解決し、最近ではより進歩的なフォークがGruntからGulpに切り替わりました。
React環境では、これらを安全に忘れることができます。 それらを使用できなかったわけではありませんが、Webpackと古き良きNPMを使用するだけでうまくいく可能性があります。 そんなことがあるものか? Webpackはモジュールバンドラーであり、Node.jsの世界で一般的なCommonJSモジュール構文をブラウザーにも実装します。 フロントエンド用にさらに別のパッケージマネージャーを学ぶ必要がないため、実際には物事が簡単になります。 NPMを使用して、サーバーとフロントエンド間で依存関係を共有するだけです。 また、JSファイルを正しい順序でロードする問題に対処する必要はありません。これは、各ファイルで指定された依存関係のインポートから推測され、スウォーム全体が1つのロード可能なスクリプトに正しく連結されているためです。
さらに魅力的なものにするために、Webpackは、以前のいとこであるBrowserifyとは異なり、他のアセットタイプも処理できます。 たとえば、ローダーを使用すると、任意のアセットファイルをJavaScript関数に変換して、参照ファイルをインライン化またはロードできます。 したがって、HTMLからアセットを手動で前処理して参照することは忘れてください。 JavaScriptからCSS/SASS / LESSファイルをrequire
するだけで、Webpackが残りを単純な構成ファイルで処理します。 Webpackには、開発Webサーバーとファイルウォッチャーも含まれています。 さらに、 package.json
の"scripts"
キーを使用してシェルワンライナーを定義できます。
{ "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }
そして、GulpとBowerを置き換えるために必要なのはこれだけです。 もちろん、アプリケーションのボイラープレートを生成するためにヨーマンを使用することもできます。 必要なもののためのYeomanジェネレーターがない場合でも落胆しないでください(ほとんどの最先端のライブラリーには、多くの場合、ジェネレーターがありません)。 それでも、GitHubからボイラープレートのクローンを作成して、ハックすることができます。
明日のECMAScript、今日
JavaScript言語開発のペースは近年大幅に向上しており、癖を取り除き、言語を安定させた後、強力な新機能が登場しています。ECMAScript6(ES6)仕様ドラフトが完成しましたが、まだ公式にはされていませんが、すでに広く採用されています。 ECMAScript 7(ES7)の作業は進行中ですが、その機能の多くは、より最先端のライブラリーによってすでに採用されています。
これはどのように可能ですか? Internet Explorerでサポートされるまで、これらの光沢のある新しいJavaScript機能を利用できないと思うかもしれませんが、もう一度考えてみてください。 ESトランスパイラーはすでに広く普及しているため、適切なブラウザーのサポートがなくても実行できます。 現在利用可能な最高のESトランスパイラーはBabelです。最新のES6+コードを取得し、それをバニラES5に変換するため、新しいES機能は、発明されたらすぐに使用できます(Babelで実装されます。これは、通常、かなり発生します。早く)。
最新のJavaScript機能は、すべてのフロントエンドフレームワークで役立ちます。最近、Reactが更新され、ES6およびES7の仕様とうまく連携するようになりました。 これらの新機能は、Reactで開発する際の多くの頭痛の種を取り除くはずです。 最も有用な追加のいくつかと、それらがReactプロジェクトにどのように役立つかを見てみましょう。 後で、この改善された構文を利用しながら、Reactでいくつかの便利なツールとライブラリを使用する方法を説明します。
ES6クラス
オブジェクト指向プログラミングは強力で広く採用されているパラダイムですが、JavaScriptのそれに対する考え方は少しエキゾチックです。 したがって、Backbone、Ember、Angular、Reactなど、ほとんどのフロントエンドフレームワークは、クラスを定義してオブジェクトを作成する独自の方法を採用しています。 しかし、ES6では、JavaScriptに従来のクラスがあり、独自の実装を作成する代わりにそれらを使用するのが理にかなっています。 したがって、代わりに:
React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })
我々は書ける:
class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }
より複雑な例として、古い構文を使用してこのコードを検討してください。
React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });
そして、ES6バージョンと比較してください。
class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }
ここでは、ReactライフサイクルメソッドgetDefaultProps
およびgetInitialState
は不要になりました。 getDefaultProps
は静的クラス変数defaultProps
になり、初期状態はコンストラクターで定義されるだけです。 唯一の欠点は、メソッドが自動バインドされなくなったことです。そのため、JSXからハンドラーを呼び出すときにbind
を使用する必要があります。
デコレータ
デコレータはES7の便利な機能です。 関数またはクラスを別の関数内にラップすることで、関数またはクラスの動作を強化できます。 たとえば、一部のコンポーネントに同じ変更ハンドラーを設定したいが、継承アンチパターンにコミットしたくないと仮定します。 代わりにクラスデコレータを使用できます。 デコレータを次のように定義しましょう。
addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }
ここで重要なのは、関数addChangeHandler
がchangeHandler
関数をターゲットクラスのプロトタイプに追加することです。
デコレータを適用するには、次のように記述できます。
MyClass = changeHandler(MyClass)
またはよりエレガントに、ES7構文を使用:
@addChangeHandler class MyClass { ... }
changeHandler
関数自体のコンテンツに関しては、Reactには双方向のデータバインディングがないため、Reactでの入力の操作は面倒な場合があります。 changeHandler
関数は、それを簡単にしようとします。 最初のパラメーターは、入力のデータオブジェクトとして機能する状態オブジェクトのkey
を指定します。 2番目のパラメーターは属性であり、入力フィールドの値が保存されます。 これらの2つのパラメーターは、 bind
キーワードを使用してJSXから設定されます。
@addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }
ユーザーがユーザー名フィールドを変更すると、その値はthis.state.login.username
に保存され、カスタムハンドラーをさらに定義する必要はありません。
矢印機能
JavaScriptの動的なthis
コンテキストは、開発者にとって常に苦痛でした。これは、ネストされた関数のthis
コンテキストが、クラス内であってもグローバルにリセットされるためです。 これを修正するには、通常、 this
を外部スコープ変数(通常は_this
)に保存し、内部関数で使用する必要があります。
class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }
新しいES6構文では、 function(x){
を(x) => {
として書き直すことができます。 この「矢印」メソッド定義は、 this
を外部スコープに正しくバインドするだけでなく、かなり短くなります。これは、多くの非同期コードを作成するときに間違いなく重要です。
onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }
割り当ての破棄
ES6で導入された非構造化割り当てを使用すると、割り当ての左側に複合オブジェクトを配置できます。
var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true
これは素晴らしいことですが、実際にReactでどのように役立ちますか? 次の例を考えてみましょう。
function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }
破棄すると、いくつかのキーストロークを節約できます。 キーリテラル{url, method, params}
には、キーと同じ名前のスコープから値が自動的に割り当てられます。 このイディオムは非常に頻繁に使用され、繰り返しを排除することでコードのエラーが発生しにくくなります。
function makeRequest(url, method, params) { var config = {url, method, params}; ... }
破棄は、モジュールのサブセットのみをロードするのにも役立ちます。
const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }
引数:デフォルト、REST、およびスプレッド
関数の引数はES6でより強力です。 最後に、デフォルトの引数を設定できます。
function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET
扱いにくいarguments
オブジェクトをいじるのにうんざりしていませんか? 新しい仕様では、残りの引数を配列として取得できます。
function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }
また、 apply()
を呼び出すのが気に入らない場合は、配列を関数の引数に広げることができます。
myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);
ジェネレーターと非同期関数
ES6はJavaScriptジェネレーターを導入しました。 ジェネレーターは基本的にJavaScript関数であり、その実行を一時停止して後で再開し、その状態を記憶することができます。 yield
キーワードが検出されるたびに、実行が一時停止され、 yield
引数が呼び出し元のオブジェクトに返されます。
function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }
このジェネレーターの動作例を次に示します。
> var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }
ジェネレーター関数を呼び出すと、最初のyield
まで実行され、その後停止します。 next()
を呼び出すと、最初の値が返され、実行が再開されます。 各yield
は別の値を返しますが、3回目の呼び出しの後、ジェネレーター関数は終了し、 next()
への後続の各呼び出しは{ value: undefined, done: true }
を返します。
もちろん、ジェネレータのポイントは、複雑な数値シーケンスを作成することではありません。 エキサイティングな部分は、関数の実行を停止および再開する機能です。これを使用して、非同期プログラムフローを制御し、最終的にこれらの厄介なコールバック関数を取り除くことができます。
このアイデアを示すために、最初に非同期関数が必要です。 通常、I / O操作がありますが、簡単にするために、 setTimeout
を使用してpromiseを返します。 (ES6はJavaScriptにもネイティブpromiseを導入したことに注意してください。)
function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }
次に、消費関数が必要です。
function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }
この関数は、任意のジェネレーターを引数として取り、 yield
する値がある限り、ジェネレーターに対してnext()
を呼び出し続けます。 この場合、生成された値はpromiseであるため、promiseが解決されるのを待ち、 loop()
で再帰を使用して、ネストされた関数間でループを実行する必要があります。
戻り値はthen()
ハンドラーで解決され、 value
に渡されます。値は外部スコープで定義され、 next(value)
呼び出しに渡されます。 この呼び出しにより、値は対応するyield式の結果になります。 これは、コールバックなしで非同期に書き込むことができることを意味します。
function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);
ジェネレーターmyGenerator
は、各yield
で一時停止され、コンシューマーが解決されたpromiseを提供するのを待ちます。 実際、計算された数値が1秒間隔でコンソールに表示されます。
Double 1 = 2 Double 2 = 4 Double 3 = 6
これは基本的な概念を示していますが、本番環境でこのコードを使用することはお勧めしません。 代わりに、coなどの十分にテストされたライブラリを選択してください。 これにより、エラー処理を含め、yieldsを使用して非同期コードを簡単に記述できます。
co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });
したがって、この例は、ES6ジェネレーターを使用してコールバックなしで非同期コードを作成する方法を示しています。 ES7は、 async
キーワードとawait
キーワードを導入し、ジェネレーターライブラリの必要性を完全に排除することで、このアプローチをさらに一歩進めています。 この機能を使用すると、前の例は次のようになります。
async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };
私の意見では、これにより、JavaScriptで非同期コードを操作する手間が省けます。 Reactだけでなく、他のすべての場所でも同様です。
ジェネレーターは、より簡潔でわかりやすいだけでなく、コールバックで実装するのが非常に難しい手法を使用することもできます。 ジェネレーターの優れた例として、Node.js用のkoaミドルウェアライブラリがあります。 Expressを置き換えることを目的としており、その目標に向けて、ミドルウェアチェーンがダウンストリーム(クライアント要求を伴う)だけでなくアップストリームにも流れるため、サーバーの応答をさらに変更できるという1つのキラー機能が付属しています。 次のkoaサーバーの例を考えてみましょう。
// Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000);
応答ミドルウェアのyield
は、応答本体を設定する応答ハンドラーの下流にあり、上流のフロー( yield
式の後)では、 this.body
のさらなる変更、および時間ロギングなどの他の機能が許可されます。アップストリームとダウンストリームが同じ機能コンテキストを共有しているためです。 これは、同じことを達成する試みが次のように終了するExpressよりもはるかに強力です。
var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);
おそらく、ここで何が問題になっているのかをすでに見つけることができます。 「グローバル」 start
変数を使用すると、競合状態が発生し、同時リクエストで意味のない結果が返されます。 解決策はいくつかの非自明な回避策であり、アップストリームフローの応答を変更することを忘れることができます。

また、koaを使用すると、ジェネレーターの非同期ワークフローを無料で利用できます。
app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);
Expressでこの小さな例を再作成する際の約束とコールバックを想像してみてください。
このNode.jsのすべての話はReactとどのように関連していますか? さて、Reactに適したバックエンドを検討するときは、Nodeが最初の選択肢です。 NodeもJavaScriptで記述されているため、バックエンドとフロントエンド間のコード共有をサポートし、同形のReactWebアプリケーションを構築できます。 しかし、これについては後で詳しく説明します。
フラックスライブラリ
Reactは、構成可能なビューコンポーネントの作成に優れていますが、アプリケーション全体でデータと状態を管理するための何らかの方法が必要です。 ReactはFluxアプリケーションアーキテクチャによって最もよく補完されることがほぼ普遍的に合意されています。 Fluxをまったく使用したことがない場合は、すばやく更新することをお勧めします。
それほど普遍的に合意されていないのは、多くのFlux実装のどれを選択するかです。 Facebook Fluxは当然の選択ですが、ほとんどの人にとっては冗長すぎます。 代替の実装は、主に設定より規約による必要なボイラープレートの量の削減に焦点を当てており、高階コンポーネントやサーバー側のレンダリングなどの便利な機能も備えています。 さまざまな人気指標を持つトップ候補のいくつかは、ここで見ることができます。 Alt、Reflux、Flummox、Fluxxor、Marty.jsを調べました。
適切なライブラリを選択する私の方法は決して客観的ではありませんが、とにかく役立つかもしれません。 Fluxxorは、私がチェックアウトした最初のライブラリの1つでしたが、今では少し古くなっているように見えます。 Marty.jsは興味深いものであり、多くの機能を備えていますが、それでも多くの定型文が含まれており、一部の機能は不要に見えます。 逆流症は見栄えがよく、ある程度の牽引力がありますが、初心者には少し難しいと感じ、適切なドキュメントもありません。 FlummoxとAltは非常に似ていますが、Altの定型文はさらに少なく、非常に活発な開発、最新のドキュメント、および役立つSlackコミュニティがあるため、Altを選択しました。
AltFlux
Altを使用すると、Fluxワークフローは、その能力を失うことなく、はるかに簡単になります。 FacebookのFluxドキュメントには、ディスパッチャーについて多くのことが記載されていますが、Altでは、ディスパッチャーは慣例により暗黙的にアクションに関連付けられており、通常はカスタムコードを必要としないため、無視してかまいません。 これにより、ストア、アクション、およびコンポーネントだけが残ります。 これらの3つのレイヤーは、 MVC思考モデルにうまくマッピングされるように使用できます。ストアはモデル、アクションはコントローラー、コンポーネントはビューです。 主な違いは、フラックスパターンの中心となる一方向のデータフローです。つまり、コントローラー(アクション)はビュー(コンポーネント)を直接変更できませんが、代わりに、ビューがパッシブにバインドされるモデル(ストア)の変更のみをトリガーできます。 これは、一部の啓蒙されたAngular開発者にとってすでにベストプラクティスでした。
ワークフローは次のとおりです。
- コンポーネントはアクションを開始します。
- ストアはアクションをリッスンし、データを更新します。
- コンポーネントはストアにバインドされ、データが更新されると再レンダリングされます。
行動
Alt Fluxライブラリを使用する場合、アクションには通常、自動と手動の2つの種類があります。 自動アクションは、 generateActions
関数を使用して作成され、ディスパッチャーに直接送信されます。 手動メソッドはアクションクラスのメソッドとして定義されており、追加のペイロードを使用してディスパッチャに送信できます。 自動アクションの最も一般的な使用例は、アプリケーションのイベントについてストアに通知することです。 手動アクションは、とりわけ、サーバーの相互作用を処理するための好ましい方法です。
したがって、RESTAPI呼び出しはアクションに属します。 完全なワークフローは次のとおりです。
- コンポーネントがアクションをトリガーします。
- アクション作成者は非同期サーバー要求を実行し、結果はペイロードとしてディスパッチャーに送られます。
- ストアはアクションをリッスンし、対応するアクションハンドラーは引数として結果を受け取り、それに応じてストアの状態を更新します。
AJAXリクエストの場合、axiosライブラリを使用できます。このライブラリは、特にJSONデータとヘッダーをシームレスに処理します。 promiseまたはcallbackの代わりに、ES7 async
/ await
パターンを使用できます。 POST
応答ステータスが2XXでない場合、エラーがスローされ、返されたデータまたは受信したエラーのいずれかがディスパッチされます。
Altワークフローの簡単な例については、ログインページを見てみましょう。 ログアウトアクションは特別なことをする必要はなく、ストアに通知するだけなので、自動的に生成できます。 ログインアクションは手動であり、アクション作成者へのパラメーターとしてログインデータを期待します。 サーバーから応答を受け取った後、成功データをディスパッチするか、エラーがスローされた場合は、受信したエラーをディスパッチします。
class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));
店舗
Fluxストアには2つの目的があります。アクションハンドラーがあり、状態を保持します。 ログインページの例を続けて、これがどのように機能するかを見てみましょう。
現在ログインしているユーザーのuser
と、現在のログイン関連error
のerrorの2つの状態属性を使用してLoginStore
を作成しましょう。 ボイラープレートを減らすという精神で、Altを使用すると、単一の関数bindActions
を使用して1つのクラスのすべてのアクションにバインドできます。
class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...
ハンドラー名は、対応するアクション名の前on
て、慣例により定義されます。 したがって、 login
アクションはonLogin
などによって処理されます。 アクション名の最初の文字はキャメルケーススタイルで大文字になることに注意してください。 LoginStore
には、対応するアクションによって呼び出される次のハンドラーがあります。
... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }
コンポーネント
ストアをコンポーネントにバインドする通常の方法は、ある種のReactミックスインを使用することです。 しかし、ミックスインは時代遅れになっているので、他の方法が必要です。 新しいアプローチの1つは、高次のコンポーネントを使用することです。 コンポーネントを取得してラッパーコンポーネント内に配置します。ラッパーコンポーネントは、ストアのリッスンと再レンダリングの呼び出しを処理します。 私たちのコンポーネントは、 props
でストアの状態を受け取ります。 このアプローチは、コードをスマートでダムなコンポーネントに編成するのにも役立ちます。これらのコンポーネントは最近流行しています。 Altの場合、コンポーネントラッパーはAltContainer
によって実装されます。
export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }
LoginPage
コンポーネントも、前に紹介したchangeHandler
デコレータを使用します。 LoginStore
からのデータは、ログインに失敗した場合のエラーを表示するために使用され、再レンダリングはAltContainer
によって処理されます。 ログインボタンをクリックすると、 login
アクションが実行され、Altフラックスワークフローが完了します。
@changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }
同形レンダリング
同形Webアプリケーションは、従来のシングルページアプリケーションの最大の雑用のいくつかを解決するため、最近話題になっています。 これらのアプリケーションでは、マークアップはブラウザのJavaScriptによって動的に作成されます。 その結果、JavaScriptがオフになっているクライアント、特に検索エンジンのWebクローラーはコンテンツを利用できなくなります。 これは、Webページがインデックスに登録されず、検索結果に表示されないことを意味します。 これを回避する方法はいくつかありますが、最適とは言えません。 同形アプローチは、サーバー上のシングルページアプリケーションの要求されたURLを事前にレンダリングすることにより、この問題を修正しようとします。 Node.jsを使用すると、サーバー上にJavaScriptがあります。つまり、Reactはサーバー側でも実行できます。 これはそれほど難しいことではありませんよね?
1つの障害は、一部のFluxライブラリ、特にシングルトンを使用するライブラリでは、サーバー側のレンダリングに問題があることです。 シングルトンのFluxストアとサーバーへの複数の同時リクエストがある場合、データは混同されます。 一部のライブラリはFluxインスタンスを使用してこれを解決しますが、これには他の欠点があります。特に、コード内でそれらのインスタンスを渡す必要があります。 AltはFluxインスタンスも提供しますが、シングルトンを使用したサーバー側のレンダリングの問題も解決しました。 各リクエストの後にストアをフラッシュするため、各同時リクエストはクリーンな状態で開始されます。
サーバー側のレンダリング機能の中核は、 React.renderToString
によって提供されます。 Reactフロントエンドアプリケーション全体もサーバー上で実行されます。 このように、クライアント側のJavaScriptがマークアップを作成するのを待つ必要はありません。 アクセスされたURL用にサーバー上に事前に構築されており、HTMLとしてブラウザに送信されます。 クライアントJavaScriptが実行されると、サーバーが中断したところから再開します。 これをサポートするために、Altで使用することを目的としたIsoライブラリを使用できます。
まず、 alt.bootstrap
を使用してサーバー上でFluxを初期化します。 Fluxストアにレンダリング用のデータを事前に入力することができます。 また、クライアント側Router
の機能である、どのURLに対してどのコンポーネントをレンダリングするかを決定する必要があります。 シングルトンバージョンのalt
を使用しているため、レンダリングするたびに、別のリクエストのためにストアをalt.flush()
してクリーンにする必要があります。 iso
アドオンを使用して、Fluxの状態がHTMLマークアップにシリアル化されるため、クライアントは次の場所を取得できます。
// We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });
クライアント側では、サーバーの状態を取得し、データを使用してalt
をブートストラップします。 次に、ターゲットコンテナでRouter
とReact.render
を実行します。これにより、サーバーで生成されたマークアップが必要に応じて更新されます。
Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })
素晴らしい!
便利なフロントエンドライブラリ
Reactエコシステムのガイドは、Reactで特にうまく機能するいくつかのフロントエンドライブラリに言及しなければ完全ではありません。 これらのライブラリは、CSSレイアウトとコンテナ、スタイル付きのフォームとボタン、検証、日付の選択など、ほとんどすべてのWebアプリケーションで見られる最も一般的なタスクに取り組みます。 これらの問題がすでに解決されている場合、車輪の再発明を行う意味はありません。
React-ブートストラップ
TwitterのBootstrapフレームワークは、CSSでの作業に多くの時間を費やしたくないすべてのWeb開発者にとって非常に役立つため、一般的になっています。 特にプロトタイピング段階では、ブートストラップが不可欠です。 To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:
<Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>
Personally, I cannot escape the feeling that this is what HTML should always have been like.
If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.
Reactルーター
React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:
<Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>
React Router also provides a Link
component that you can use for navigation in your application, specifying only the route name:
<nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>
There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active
class on them manually all the time, you can use react-router-bootstrap and write code like this:
<Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>
No additional setup is necessary. Active links will take care of themselves.
Formsy-React
Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:
import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }
Calendar and Typeahead
Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:
import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
Conclusion - React.JS
In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.
If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.
読んでくれてありがとう!