mobxをM odelとして使用する反応アプリケーションのプラットフォームを作成するプロセスについてお話したいと思います。 空のプロジェクトディレクトリから実際のサンプルに移動します。 開発プロセス中に私が注意を払った主なポイントを考慮してください。 修飾リンクでテキストを飽和させようとします。追加のメモは、 「メモ:」とマークされた斜体で強調表示されます。
ストーリーは2つの部分で構成されます。
- 反応アプリケーションのためのブリッジヘッドの準備
- Mobx +反応、側面図
「見たとおりに」書くので、改善に関する提案やコメントを歓迎します。 読者がnpm、node.js、react.jsが何であるかを知っており、小道具と状態の基本的な知識を持っていることを願っています。 この記事を書いている時点では、windowsと不安定なnode.js 7.3.0バージョンがあります。
反応アプリケーションのためのブリッジヘッドの準備
反応するスケルトンとボイラープレートが何千もあるので、「fb」でさえ、ブラックジャックとホットリロードで自分自身をリリースしました。 完成したものは使用しませんが、すべてを自分の手で収集し、その仕組みを確認します。 私たちは独立してこの方法で、すべての暗いコーナーを調べて、プロセスの全体的なメカニズムを理解し、以前は理解できなかった詳細を理解します。 私は別の自転車のふりをするのではなく、教育のために開発するのです。 熱意に圧倒され、お気に入りのIDEでコンソールを開き、プロジェクト用の新しいディレクトリを作成して中に入ります。 行こう!
npm init
ここではすべてが簡単です。いくつかの一般的な質問が提示され、その後npmがpackage.jsonの依存関係管理ファイルを作成します。
注:毎回自分に関する情報を入力しないようにするには、登録することができます
npm set init.author.name "your name" npm set init.author.email "your email" npm set init.author.url "your site url"
次に、必要な反応パッケージをインストールし、依存関係セクションのpackage.jsonにそれらについて記述します。 react-routerを使用するので、すぐにそれを置きます:
npm i --save react react-dom react-router
プロジェクトの構造を作成し、いくつかのディレクトリを作成します。 ほとんどの場合、クライアントサーバーアプリケーションが必要です。 クライアント部分とサーバー部分に2つの個別のディレクトリが必要です。ここでは、無限に長くホリビットできます。たとえば、次の構造を選択しました。
スケルトンには次のものが必要です。
- index.jsは、クライアントアプリケーションへのエントリポイントです。 これは、私が見ているアプリケーションの最初のファイルです。他の人のプロジェクトを初めて見た場合、これはあなたがもつれ全体を解き明かし始めるスレッドです。
- routes.js-ルーターのセットアップ。 開始するには、1つのルートで十分です
- home.js-ホームページ;
- index.html- SPAを実行します。index.html-これが唯一のページです。
index.html
<!doctype html> <html> <head> </head> <body> <div id="app"></div> </body> </html>
ここでは、div#アプリに注意する価値があります。これは、将来のリアクションアプリケーションのコンテナです。 少し後でスクリプトをここに追加します。
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import AppRouter from './routes'; ReactDOM.render(<AppRouter />, document.getElementById("app"));
<AppRouter />を同じdiv#アプリにレンダリングします。
ビュー/ home.js es6
import React from 'react'; export default class Home extends React.Component { render() { return ( <h1>Hello Kitty!</h1> ); } }
ルートの前に、ホーム(そしてこれまでのところ唯一の)ビューを見てみましょう。 これは、挨拶メッセージを表示するだけの反応コンポーネントです。
反応コンポーネントを作成するときにES6の方法を使用します。 ES6で友達を反応させる方法は、 ドキュメントまたはロシア語で見つけることができます。 特にトピックは理解しやすいので、すぐにES6で書くことをお勧めします。
もちろん、便宜上、jsx表記を使用します。 ブラウザーがコードを理解するために、私たちはbabelトランスレーターを使用します。さらに、時間に遅れずにES6 / ES2015の機能を使用したいのですが、すべてのブラウザーがこの標準をサポートしているわけではないので、再びbabelに助けを求めます。 babelは、新しい標準で記述されたコードをes5標準コードに書き換えるトランスパイラーであり、ほとんどすべてのブラウザーを理解し、反応するjsxコードをブラウザーが理解できるコードに変換することもできます。 それでも、それはプラグインの束をサポートしています。 これはとてもクールです!
注:この変換の魔法はすべてオンラインでも感じることができます。
reactまたはes6のコードを貼り付けて、たとえばhome.jsのコードに変換されるものを確認してください
この手順を実行すると、ES6コードの9行(〜400バイト)が44行になったことに気付くかもしれません! ES5文字列(〜2200バイト)
javascriptにはクラスがないため、これは構文糖衣に対する報復です。 babelが軽い手でクラス外の関数を作成した様子を観察できます。
おそらく、この段階では、ステートレスコンポーネントについて2、3行を言う必要があります。 大まかに言って、これらは状態を持たないコンポーネントと呼ばれます。 Homeコンポーネントには状態がないため、次のように書き換えることができます。
ステートレスhome.js
import React from 'react'; const Home = (props) => { return ( <h1>Hello Kitty</h1> ); }; export default App;
クラスを削除したため、このコードは最終的なES5構文でははるかに短くなり、そのボリュームは5倍以上減少します。 さらに、ソースコードをさらに簡潔にすることができます。
ステートレスhome.js
import React from 'react'; const Home = () => ( <h1>Hello Kitty</h1> ); export default App;
注:ステートレスコンポーネントに関するこの記事は気に入っています。お勧めします。
routes.js
import React from 'react'; import { Router, Route, browserHistory } from 'react-router'; import Home from './views/home'; export default () => ( <Router history={browserHistory}> <Route path='/' component={Home} /> </Router> );
最後に、ルートでは、ホームコンポーネントへのパスを1つだけ登録します。 ここでは疑問は生じないはずです。ライブラリはシンプルですが、同時に強力な機能を備えています 。
プロジェクトは多くのファイルで構成されているので、読み取り可能なコードをブラウザーに渡すのは戦いの半分に過ぎず、最終的には最小化されたjsファイル(index.htmlに接続します)が1つだけ必要であり、モジュールビルダーも必要になります webpackを使用して収集します 。
私たちはそれを置きます:
npm i --save-dev webpack
注:webpackはdevDependenciesセクションに配置されることに注意してください。
開発に関連し、本番環境で使用されないものはすべて--save-devフラグで設定されます。多くの場合、これらはコレクターとプラグイン、テスト、リンター、ローダー、ポスト/プリプロセッサーなどです。
すべてのコード変換について上で説明したように、そのためにbabelと必要なプリセット(プラグインのセット)が必要です。
npm i --save-dev babel-loader babel-core babel-preset-es2015 babel-preset-react
Webpackには構成ファイルが必要で、プロジェクトディレクトリのルートにwebpack.config.jsを作成します。
webpack.config.js
var webpack = require('webpack'); module.exports = { entry: './client/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, module: { loaders: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ } ] } };
また、babelrcは.babelrcファイルの使用を推奨しています。ここでは、使用するプリセットを説明します。
{ "presets": ["es2015", "react"] }
注:いくつかの便利なリンク:babel 6について知っておく必要のある6つのことと、 プリセットが宣言される順序の違いは何ですか?
ここで、アプリケーションへのエントリポイントがclient / index.jsファイルであることをコレクターに伝えます。webpackはこのファイルから作業を開始します。アセンブリに含めるファイルを指定する必要はありません。 出力は、パブリックディレクトリにある1つのbundle.jsファイルです。 大まかに言って、この構成ではbabelと言います:「ねえ、index.jsから始めて、すべての必要なファイルを1つに接着し、babelがすべての.jsおよび.jsxファイルをブラウザーが理解できるコードに変換するようにします。」すごい? Webpack構成の準備ができたら、コンソールに移動してコレクターを実行します。
webpack
bundle.jsファイルは、パブリックディレクトリに表示されます。 Publicは私たちのパブリックディレクトリです。ビルド後、すべての「既製」ファイル(私たちにとってはindex.html + bundle.js)がここに来るはずです。 バンドルの準備ができました。htmlを実行します。 現在のindex.htmlは単なるワークピースであり、将来的には、たとえば、CSSまたはjsファイルを添付し、コンテンツを最小化または追加し、異なるアセンブリに対して異なる操作を実行する必要があることを理解する必要があります。 これらの目的のために、 HtmlWebpackPluginが必要です 。 私たちはそれを置きます:
npm i --save-dev html-webpack-plugin
構成ファイルに移動してプラグインを構成した後:
webpack.config.js
var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './client/index.js', output: { path: __dirname + '/public', filename: 'bundle.js' }, module: { loaders: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, } ] }, plugins: [ new HtmlWebpackPlugin({ template: './client/index.html', inject: "body" }) ] };
これは、index.htmlスタブに組み込まれるbundle.jsへのリンクを含む 'script'タグを挿入するようにwebpackに指示するものです。 この場合、「準備完了」のindex.htmlはバンドルの隣、つまり公開されます。 再度webpackを実行し、パブリックディレクトリを確認してこれを確認します。
バーと私たちを集めたwebpackに戻りましょう。 気配りのある読者は、「ハローキティ!」には〜710KBが少し大きいことに気付くでしょう。 同意しますが、開発者がコンソールにさまざまな警告を表示するなどの追加機能を提供する開発バージョンがまだあります。 プロダクション用にプロジェクトを組み立てたいという反応をほのめかしてみましょう。 これを行うには、最終的なbundle.jsを最小化し、NODE_ENV環境変数を「production」に設定します。 構成にプラグインを追加すると、追加のものをダウンロードしてインストールする必要はありません。
webpack.config.js
plugins: [ new webpack.DefinePlugin({ "process.env": { NODE_ENV: JSON.stringify("production") } }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: true } }), new HtmlWebpackPlugin({ template: './client/index.html', inject: "body" })]
プラグインの完全なリストはこちらをご覧ください 。
注:NODE_ENV = productionを設定せず、単にファイルを圧縮する場合、reactはコンソールに警告を表示します:
プラグインを使用してプロジェクトを再構築し、新しいバンドルを再度確認します。
すでにこれで作業できますが、これは制限ではありません。別のwebpack構成設定-" devtool "を見てみましょう。 このオプションは、最終的なファイルサイズとビルド速度にも影響します。 したがって、製品と開発に異なる値を使用します。 ここでは、各オプションの仕組みを読むことができます。 私は、生産のために「ソースマップ」を選択し、開発のために「インラインソースマップ」を選択しましたが、おそらくプロジェクトによってこれらの値は異なる場合があります。 ここでは、自分でプレイして最高のものを選択する必要があります。
さまざまなニーズに合わせてプロジェクトを簡単に組み立てることができるようになったため、構成ファイルを変更する時が来ました。条件を介して1つの構成で設定を調整するときの解決策が嫌いです アセンブリの設定やタイプを増やすと、構成が読みにくくなるため、 webpack-configを使用します。
npm install --save-dev webpack-config
説明からわかるように、これは構成ファイルのロード、展開、およびマージのアシスタントです。 この例では、開発と本番の2つのアセンブリを実行できるようにしたいと考えています。 図に示すように、confディレクトリと3つの構成ファイルを追加します。
webpack.base.config.js
import Config from 'webpack-config'; import HtmlWebpackPlugin from 'html-webpack-plugin'; export default new Config().merge({ entry: './client/index.js', output: { path: __dirname + '/../public', }, module: { loaders: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, } ] }, plugins: [ new HtmlWebpackPlugin({ template: './client/index.html', inject: "body" })] });
基本構成には、2つのアセンブリに有効な一般設定があります。
webpack.development.config.js
import Config from 'webpack-config'; export default new Config().extend('conf/webpack.base.config.js').merge({ output: { filename: 'bundle.js' } });
開発設定では、最終的なバンドルの名前「bundle.js」のみを指定します
webpack.production.config.js
import webpack from 'webpack'; import Config from 'webpack-config'; export default new Config().extend('conf/webpack.base.config.js').merge({ output: { filename: 'bundle.min.js' }, plugins: [ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: true } })] });
本番環境では、プラグインを追加して最小化し、バンドルの名前を変更します。 ご覧のとおり、両方の設定がベースから展開されます。
webpack.config.js
import Config, { environment } from 'webpack-config'; environment.setAll({ env: () => process.env.NODE_ENV }); export default new Config().extend('conf/webpack.[env].config.js');
NODE_ENV環境変数を使用してアセンブリを管理できるようになりました。その値に応じて、webpack-configは必要なファイルを自動的にプルアップします。
注:webpack.config.jsはES6構文を使用するため、webpackを起動しようとすると、「SyntaxError:Unexpected token import」というエラーが表示されます。 問題を解決するには、このファイルの名前をwebpack.config.babel.jsに変更するだけです。 これにより、設定をbabel-loaderに渡します。
scriptsセクションのpackage.jsonに必要なwebpack起動スクリプトを追加します。
"scripts": { "build-dev": "set NODE_ENV=development&& webpack --progress", "build-prod": "set NODE_ENV=production&& webpack --progress" },
--progressフラグを使用すると、バンドルの進行状況とレポートを確認できます。 これで、2つの異なるアセンブリを組み立てることができます。 製品の場合:
npm run build-prod
および開発用:
npm run build-dev
注:私はウィンドウで作業するため、割り当ては「set NODE_ENV = production」のようになります。 他のオペレーティングシステムの場合、割り当ては異なります。
ホットローダー-最後のタッチがあります。 このことにより、ソースファイルを変更するときにプロジェクトをその場で再構築できます。 この場合、ページはリロードされず、状態は失われません。 これにより、開発がスピードアップし、開発プロセスが喜びに変わります。 このポッドキャストで詳細を聞くことができます。このトピックに関する興味深いリソースへのリンクもあります。
このために必要なのは、 react-hot-loader 、 webpack-dev-middleware 、 webpack-hot-middleware 、そしてもちろんサーバー自体です。expressを使用します。
npm i --save express
npm i --save-dev react-hot-loader@next webpack-dev-middleware webpack-hot-middleware
注: 次のバージョンのreact-hot-loaderをインストールする必要があることに注意してください。
ファイルをプロジェクトルートに追加します
server.js
import express from 'express'; import path from 'path'; const PORT = 7700; const PUBLIC_PATH = __dirname + '/public'; const app = express(); const isDevelopment = process.env.NODE_ENV === 'development'; if (isDevelopment) { const webpack = require('webpack'); const webpackConfig = require('./webpack.config.babel').default; const compiler = webpack(webpackConfig); app.use(require('webpack-dev-middleware')(compiler, { hot: true, stats: { colors: true } })); app.use(require('webpack-hot-middleware')(compiler)); } else { app.use(express.static(PUBLIC_PATH)); } app.all("*", function(req, res) { res.sendFile(path.resolve(PUBLIC_PATH, 'index.html')); }); app.listen(PORT, function() { console.log('Listening on port ' + PORT + '...'); });
最小のエクスプレスサーバー、唯一の注意点は、アセンブリの開発用のミドルウェアのセットアップです 。 ご覧のとおり、ミドルウェアのデータはwebpack.config.babelから取得されます
次のステップは、プラグインセクションを.babelrcに追加することです
"plugins": [ "react-hot-loader/babel" ]
開発用の構成ファイルは次のようになります。
webpack.development.config.js
import webpack from 'webpack'; import Config from 'webpack-config'; export default new Config().extend('conf/webpack.base.config.js').merge({ entry: [ 'webpack-hot-middleware/client?reload=true', 'react-hot-loader/patch', __dirname + '/../client/index.js' ], devtool: 'inline-source-map', output: { filename: 'bundle.js' }, plugins: [ new webpack.HotModuleReplacementPlugin() ] });
また、変更を受けました
index.js
import React from 'react'; import { AppContainer } from 'react-hot-loader'; import ReactDOM from 'react-dom'; import AppRouter from './routes'; const render = (Component) => ReactDOM.render( <AppContainer> <Component /> </AppContainer>, document.getElementById('app') ); render(AppRouter); if (module.hot) { module.hot.accept('./routes', () => { require('./routes'); render(AppRouter); }); }
最後に、package.jsonのスクリプトは次のようになります。
"scripts": { "build-dev": "set NODE_ENV=development&& node server.js", "build-prod": "set NODE_ENV=production&& webpack && node server.js" },
注:スクリプトを実行しようとすると、「SyntaxError:Unexpected token import」というエラーが再び表示されます。 server.jsはES6インポートを使用し、webpack.config.babel.jsを読み取ろうとするため、インポートも使用します。 また、サポートは8番目のバージョンでのみ約束されます。 babel-cliコマンドラインにはBabelが必要です。
npm i --save-dev babel-cli
nodeではなくbabel-nodeを使用します。すべてが機能するはずです。
"scripts": { "build-dev": "set NODE_ENV=development&& babel-node server.js", "build-prod": "set NODE_ENV=production&& webpack && babel-node server.js" },
両方のアセンブリを収集しようとしていますが、本番では最小化されたbundle.min.jsが収集され、サーバーはポート7700で起動します。開発ではホットリブートが機能しますが、パブリックディレクトリにはファイルが表示されず、プロセス全体がメモリで実行されます。 テストのために、コードhome.jsを複雑にしましょう
home.js
import React from 'react'; export default class Home extends React.Component { constructor() { super(); this.state = { name: "Kitty" }; this.clickHandler = this.clickHandler.bind(this); } clickHandler() { this.setState({ name: "Bunny" }); } render() { return ( <h1 onClick={this.clickHandler}> {`Hello ${this.state.name}!`} </h1> ); } }
ところで、開発アセンブリを開始した場合は、すべての変更をすぐにプルする必要があります。 ヘッダーをクリックして、名前の状態を「Kitty」から「Bunny」に変更し、コード内でヘッダーのテキストを「Hello」から「Bye」に置き換えます。 ブラウザに移動して、「Bye Bunny」という碑文が表示されます。 ホットリブートは機能しましたが、変更された状態はリセットされませんでした。
最初はCSSで作業を追加したくありませんでしたが、記事を書いている過程で、すべてのセットで同じようにスタイルを構築するプロセスを追加する必要があることに気付きました。
おそらく、ある場所でレイアウトを編集し、別の場所で静かに新しい問題を作成したり、スタイルが互いに上書きしたり、上記で説明したものと同じクラスを使用したりする場合がありました。 反応するコンポーネントを作成するので、グローバルではなくコンポーネントにすぐにCSSを使用してみませんか? CSSモジュールを使用します ! post-cssとそのプラグインが必要になります 。 まず、開発を高速化するためのautoprefixerとprecssに興味があります:
npm i --save-dev css-loader style-loader postcss-loader autoprefixer precss
構成を変更する
webpack.base.config.js
import webpack from 'webpack'; import Config from 'webpack-config'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import autoprefixer from 'autoprefixer'; import precss from 'precss'; export default new Config().merge({ entry: './client/index.js', output: { path: __dirname + '/../public', }, module: { loaders: [ { test: /.jsx?$/, loader: 'babel-loader', exclude: /node_modules/, } ] }, plugins: [ new HtmlWebpackPlugin({ template: './client/index.html', inject: "body" }), new webpack.LoaderOptionsPlugin({ options: { postcss: [precss, autoprefixer] } }) ] });
webpack.development.config.js
import webpack from 'webpack'; import Config from 'webpack-config'; export default new Config().extend('conf/webpack.base.config.js').merge({ entry: [ 'webpack-hot-middleware/client?reload=true', 'react-hot-loader/patch', __dirname + '/../client/index.js' ], devtool: 'inline-source-map', output: { filename: 'bundle.js' }, module: { loaders: [{ test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: true, importLoaders: 1, localIdentName: "[local]__[hash:base64:5]", minimize: false } }, { loader: 'postcss-loader' }, ] }] }, plugins: [ new webpack.HotModuleReplacementPlugin() ] });
webpack.production.config.js
import webpack from 'webpack'; import Config from 'webpack-config'; export default new Config().extend('conf/webpack.base.config.js').merge({ output: { filename: 'bundle.min.js' }, devtool: 'source-map', module: { loaders: [{ test: /\.css$/, use: [ 'style-loader', { loader: 'css-loader', options: { modules: true, importLoaders: 1, localIdentName: "[hash:base64:10]", minimize: true } }, { loader: 'postcss-loader' }, ] }] }, plugins: [ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: true } })] });
基本設定にプラグインを追加し、残りのローダーを追加します。設定のみが異なります。 ここでlocalIdentNameオプションは興味深いもので、CSSクラス名を設定できます。プロダクションバージョンでは10文字のハッシュを使用し、開発者ではクラス名+ 5文字のハッシュを使用します。 これは非常に便利です。デバッグするときに、修正する必要があるクラスが常にわかるからです。 たとえば、Menuコンポーネントを追加しましょう。
プロジェクト構造
メニュー/index.js
import React from 'react'; import styles from './style.css'; const Menu = () => ( <nav className={styles.menu}> <div className={styles['toggle-btn']}>☰</div> </nav> ); export default Menu;
cssモジュールの使用方法に注意してください。 これらはローカルスタイルです。つまり、別のメニューの場合、他のスタイルで.menuクラスを使用することもできます。これらは交差しません。
menu / style.css
.menu { position: fixed; top: 0; left: 0; bottom: 0; width: 40px; background-color: tomato; & .toggle-btn { position: absolute; top: 5px; right: 10px; font-size: 26px; font-weight: 500; color: white; cursor: pointer; } }
app.js
import React from 'react'; import Menu from '../components/menu'; // Global CSS styles import './global.css'; const App = () => ( <div className="app-container"> <Menu /> <div className="page-container"></div> </div> ); export default App;
ただし、たとえばhtmlやbodyに対して「グローバル」スタイルを使用することもできます。 app.jsを接続するだけで十分です。
routes.js
import React from 'react'; import { Router, Route, browserHistory } from 'react-router'; import App from './views/app'; import Home from './views/home'; export default () => ( <Router history={browserHistory}> <Route path='/' component={App}> <Route path='home' component={Home} /> </Route> </Router> );
ネストを少し追加して、ホームページがネストされたAppコンテナを用意しました。
Webpackの設定では、スタイルのホットリロードを使用することもできます。スタイルを変更するだけです。
これは終了した最初の部分です。 私は最も正しい選択肢のふりをするつもりはありませんが、この記事を書いている間にこの方法でいくつかの興味深いことを学びました。
起こったことへのリンク: github.com/AlexeyRyashencev/react-hot-mobx-es6