妻の目を通して同形アプリケーションを開発する
これは、 React.jsで同形アプリケーションをゼロから開発することに関する記事の続きです。 このパートでは、いくつかのページ、 ブートストラップ 、ルーティング、 Fluxコンセプト、およびReduxの一般的な実装を追加します。
目次
1) 同型アプリケーションの基本スタックを構築する
2)ルーティングとブートストラップを使用した簡単なアプリケーションを作成します
3) APIと認証との相互作用を実装します
そのため、最初の部分では、単純なHelloWorldコンポーネントを開発し、コード品質を構築および監視するための環境を構築することになりました。 本格的なWebサイトを作成するときです。つまり、さらにいくつかのページを追加し、それらをリンクし、同形ルーティングを実装します。
1. react-bootstrapをプロジェクトに追加します
これは、 Reactスタイルのブートストラップ要素を使用できる非常に人気のあるライブラリです。
たとえば、ビュー構造の代わりに
<div className="nav navbar">
私たちは書くことができます
<Nav navbar>
また、オリジナルのbootstrapの JavaScriptコードは、 react-bootstrapコンポーネントに既に実装されているため、使用する必要はありません。
react-bootstrapをインストールする
npm i --save react-bootstrap
プロジェクトに変更を加えます
HelloWorldウィジェットをApp.jsxから個別のコンポーネントに分離します。 App.jsxはアプリケーションの同形部分へのエントリポイントであり、すぐにレイアウトとして書き直し、その中にユーザーが要求したページが表示されることを思い出します。
リファクタリング
- src / components / HelloWorldPageフォルダーを作成します
- App.jsxの名前をHelloWorldPage.jsxに変更し、 App.cssの名前をHelloWorldPage.cssに変更します
- HelloWorldPage.jsxおよびHelloWorldPage.cssファイルをsrc / components / HelloWorldPageフォルダーに移動します
mkdir src/components/HelloWorldPage mv src/components/App.jsx src/components/HelloWorldPage/HelloWorldPage.jsx mv src/components/App.css src/components/HelloWorldPage/HelloWorldPage.css
- HelloWorldPage.jsxに変更を加えます
--- import './App.css'; +++ import './HelloWorldPage.css';
- 次の内容のindex.jsファイルを作成します
src / components / HelloWorldPage / index.js
import HelloWorldPage from './HelloWorldPage'; export default HelloWorldPage;
この手順により、コンポーネントをインポートできるようになります。
import HelloWorldPage from 'components/HelloWorldPage';
の代わりに
import HelloWorldPage from 'components/HelloWorldPage/HelloWorldPage';
これはより正確で、アプリケーションのソースコードのメンテナンスを簡素化します。
App.jsxを作成する
- アプリフォルダーを作成する
- Appフォルダーに、 index.jsとApp.jsxの 2つのファイルを作成します
mkdir src/components/App
src / components / App / index.js
import App from './App'; export default App;
src / components / App / App.jsx
import React, { Component } from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import NavItem from 'react-bootstrap/lib/NavItem'; import HelloWorldPage from 'components/HelloWorldPage'; class App extends Component { render() { return ( <div> <Navbar> <Navbar.Header> <Navbar.Brand> <span>Hello World</span> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav navbar> <NavItem></NavItem> <NavItem></NavItem> </Nav> </Navbar.Collapse> </Navbar> <Grid> <HelloWorldPage /> </Grid> </div> ); } } export default App;
重要な注意:どのreact-bootstrapコンポーネントをインポートするか明示的に述べていることに注意してください。 これにより、ビルドプロセスでwebpackがプロジェクトで使用されるreact-bootstrap部分のみを含め、ライブラリ全体ではなく、私が書いた場合のようになります。
import { Grid, Nav, Navbar, NavItem } from 'react-bootstrap';
この操作は、使用するライブラリがモジュール方式をサポートしている場合にのみ機能することに注意することが重要です。 たとえば、 react-bootstrapとlodashはこれらに属しますが、 jqueryとmomentjsは属しません。
コードからわかるように、上記のコンポーネントは状態で動作せず、 コンポーネントワークフローコールバック ( componentWillMountやcomponentDidMountなど )を使用しません。 これは、いわゆるPure Sateless Function Componentとして書き換えられることを意味します 。
将来的には、この方法で記述されたコンポーネントにはパフォーマンス上の利点があります(関数型プログラミングの理論と純粋な関数の概念のおかげです)。各コンポーネントの生産性が高いほど、アプリケーションの生産性は向上します。
それまでの間、試薬はこれらのコンポーネントを通常のES6クラスにラップしますが、1つの素晴らしいボーナスがあります。
デフォルトでは、新しい小道具や状態値が以前のものと完全に一致する場合でも、新しい小道具や状態の値が受信されると、コンポーネントは常に更新されます。 これは必ずしも必要ではありません。 開発者には、 trueまたはfalseを返すshouldComponentUpdate(nextProps、nextState)メソッドを独立して実装する機会があります。 これを使用すると、コンポーネントを再描画する場合としない場合に、Reactに明示的に指示できます。
コンポーネントがPure Stateless Function Componentとして実装されている場合、React自体は、 shouldComponentUpdateの明示的な実装なしでコンポーネントの外観を更新する必要があるかどうかを判断できます。 つまり 、少ない労力で大きな利益を得ることができます。
注:以下のコードは、このようなコンポーネントのトレーニング例です。 将来的にはApp.jsxに変更を加え 、 純粋なステートレスコンポーネントではなくなるため、この例をプロジェクトに転送しないでください。
注2:このプロジェクトでは、記事の内容が複雑にならないように、 純粋なステートレス関数コンポーネントの形式で実装することが可能であり、正しい場合でも、すべてのコンポーネントをES6クラスの形式で実装します。
import React from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import NavItem from 'react-bootstrap/lib/NavItem'; import HelloWorldPage from './HelloWorldPage'; function App() { return ( <div> <Navbar> <Navbar.Header> <Navbar.Brand> <span>Hello World</span> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav navbar> <NavItem></NavItem> <NavItem></NavItem> </Nav> </Navbar.Collapse> </Navbar> <Grid> <HelloWorldPage /> </Grid> </div> ); } export default App;
ブラウザーで何が変わったのかを見てみましょう。 そして...はい、 ブートストラップにはスタイルがありません。 独自のテーマを使用するため、 react-bootstrap開発者は意図的にディストリビューションにそれらを含めませんでした。 したがって、 bootstrap.comなどのbootstrapのテーマがあるサイトにアクセスして、好きなサイトをダウンロードします。 src / components / App / bootstrap.cssに保存します。 カスタマイズが簡単なので、フルバージョンを保持することをお勧めします。 そうすると、とにかくwebpackによって縮小が行われます 。
注: githubのリポジトリからテーマをダウンロードできます。
App.jsxに変更を加えます
src / components / App / App.jsx
+++ import './bootstrap.css';
特にグリフコンをプロジェクトで使用しないため、 グリフィコンの作業のセットアップに集中したくないので、 グリフィコンをスタイルから削除します。
src / components / App / bootstrap.css
--- @font-face { --- font-family: 'Glyphicons Halflings'; --- src: url('../fonts/glyphicons-halflings-regular.eot'); src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); --- }
ブラウザに戻ると、すべてが正常に表示されるはずです。
注:ページをリロードするときに、古いバージョンのページが最初に表示され、数秒後に新しいバージョンが表示されるので、 nodemonを再起動してください 。
2.いくつかのページとルーティングを追加します。
2.1。 2つのスタブを作りましょう
- src / components / CounterPageおよびsrc / components / TimePageフォルダーを作成します
- スタブのコードを書く
src / components / CounterPage / index.js
import CounterPage from './CounterPage'; export default CounterPage;
src / components / CounterPage / CounterPage.jsx
import React, { Component } from 'react'; class CounterPage extends Component { render() { return <div> </div>; } } export default CounterPage;
src / components / TimePage / index.js
import TimePage from './TimePage'; export default TimePage;
src / components / TimePage / TimePage.jsx
import React, { Component } from 'react'; class TimePage extends Component { render() { return <div> </div>; } } export default TimePage;
2.2。 ルーティングを追加
ルーティングには、 react-routerライブラリを使用します 。
npm i --save react-router
動作させるには、プロジェクトに次の変更を加える必要があります。
- routesでファイルを定義します。 その中で、 URLとレンダリングされるべきコンポーネントとの対応を示します。
- アプリケーションのサーバー側で、 エクスプレス Webサーバーは要求URLをreact-routerから一致関数に渡します 。 renderPropsを返します 。これを使用して、ユーザーが要求したコンテンツをレンダリングするか、一致しないことを報告してから、404エラーのページを返します。
- また、 react-routerライブラリがURLの変更を追跡できるように、アプリケーションのクライアント部分に変更を加えます。 新しいURLが設定されたパスの1つと一致する場合、クライアント側のJavaScriptはサーバーにアクセスせずにページコンテンツを更新します。 新しいURLが設定されたパスのいずれとも一致しない場合、ブラウザはクラシックリンクをクリックします。
2.2.1。 ルートファイル
src / routes.jsx
import React from 'react'; import { IndexRoute, Route } from 'react-router'; import App from 'components/App'; import CounterPage from 'components/CounterPage'; import HelloWorldPage from 'components/HelloWorldPage'; import TimePage from 'components/TimePage'; export default ( <Route component={App} path='/'> <IndexRoute component={HelloWorldPage} /> <Route component={CounterPage} path='counters' /> <Route component={TimePage} path='time' /> </Route> );
実際、Reactコンポーネントをエクスポートしていることに注意してください。 IndexRouteは、web上のindex.htmlまたはindex.phpに類似しています。パスの一部が省略されている場合は、選択されます。
注: RouteおよびIndexRouteコンポーネントは、他のRouteに何度でもネストできます。 この例では、2つのレベルに制限しています。
したがって、次の対応を決定しました
URL '/' =>タイプ<HelloWorldPage />のコンポーネント
URL '/ counter' => <CounterPage />
URL '/ time' => <TimePage />
このアプリケーションでは、 Appコンポーネントがレイアウトの役割を果たす必要があります。そのため、埋め込みコンポーネント( 子 )をレンダリングするためにそれを「教える」必要があります。
src / components / App / App.jsx
--- import React, { Component } from 'react'; +++ import React, { Component, PropTypes } from 'react'; import Grid from 'react-bootstrap/lib/Grid'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import NavItem from 'react-bootstrap/lib/NavItem'; --- import HelloWorldPage from 'components/HelloWorldPage'; import './bootstrap.css'; +++ const propTypes = { +++ children: PropTypes.node +++ }; class App extends Component { render() { return ( <div> <Navbar> <Navbar.Header> <Navbar.Brand> <span>Hello World</span> </Navbar.Brand> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav navbar> <NavItem></NavItem> <NavItem></NavItem> </Nav> </Navbar.Collapse> </Navbar> <Grid> +++ {this.props.children} --- <HelloWorldPage /> </Grid> </div> ); } } +++ App.propTypes = propTypes; export default App;
2.2.2アプリケーションのサーバー側にルーティングを追加する
src / server.js
--- import App from 'components/App'; +++ import { match, RouterContext } from 'react-router'; +++ import routes from './routes'; app.use((req, res) => { --- const componentHTML = ReactDom.renderToString(<App />); +++ match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { +++ if (redirectLocation) { // redirect +++ return res.redirect(301, redirectLocation.pathname + redirectLocation.search); +++ } +++ if (error) { // +++ return res.status(500).send(error.message); +++ } +++ if (!renderProps) { // , URL +++ return res.status(404).send('Not found'); +++ } +++ const componentHTML = ReactDom.renderToString(<RouterContext {...renderProps} />); +++ return res.end(renderHTML(componentHTML)); +++ }); --- return res.end(renderHTML(componentHTML)); });
注: match関数は、最初のJavaScriptパラメーターとしてルートとロケーションキーを持つオブジェクトを受け入れます。 私はショートハンド表記ES6を使用しています、フルバージョンは次のようになります
{ routes: routes, location: req.url},
ここで、 routes.jsxファイルからルートをインポートします。 2番目のパラメーターとして、 matchはレンダリングを行うコールバック関数を取ります。
クライアント側のJavaScriptを一時的に無効にする
src / client.js
// ReactDOM.render(<App />, document.getElementById('react-view'));
ブラウザでルーティングをテストしてみましょう。ページは同じように見えますが、 AppコンテナにHelloWorldPageコンポーネントを明示的に埋め込む必要はありません。 先に進みます。
他のページへのリンクを追加します。 通常、これは次のように行われます。
import { Link } from 'react-router'; <Link to='/my-fancy-path'>Link text</Link>
ただし、 NavItemコンポーネントをリンクとしてリンクする必要があります。 これを行うには、 react-router-bootstrapライブラリを使用します。
npm i --save react-router-bootstrap
src / components / App / App.jsx
+++ import { Link } from 'react-router'; +++ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; --- <span>Hello World</span> +++ <Link to='/'>Hello World</Link> --- <NavItem></NavItem> +++ <LinkContainer to='/time'> +++ <NavItem></NavItem> +++ </LinkContainer> --- <NavItem></NavItem> +++ <LinkContainer to='/counters'> +++ <NavItem></NavItem> +++ </LinkContainer>
サーバーのルーティングをテストします。
nodemonを再起動します 。 ブラウザーで、「 開発ツール」 、「 ネットワーク」タブを開きます。
これで、作業の結果を評価して、ナビゲーションメニューのリンクをクリックできます。 要求は 、 エクスプレスが処理されるサーバーに送られることに注意してください。 次に、要求されたページのHTMLコードをレンダリングしてブラウザーに返します。 これで、アプリケーションは従来のWebアプリケーションとまったく同じように機能します。
クライアントのJavaScriptをロードして初期化する時間がない場合、またはエラーが発生した場合、アプリケーションは正常に機能します。
2.2.3アプリケーションのクライアント部分にルーティングを追加します。
src / client.js
--- import App from 'components/App'; +++ import { browserHistory, Router } from 'react-router'; +++ import routes from './routes'; --- // ReactDOM.render(<App />, document.getElementById('react-view')); +++ const component = ( +++ <Router history={browserHistory}> +++ {routes} +++ </Router> +++ ); +++ ReactDOM.render(component, document.getElementById('react-view'));
注: ルーターコンポーネントがアプリケーションのルートコンポーネントになったことに注意してください。 URLの変更を追跡し、設定したルートに基づいてページコンテンツを生成します。
ブラウザーに戻り、リンクを再度クリックして、 Developer ToolsツールのNetworkタブを注意深く観察します。 今回は、ページはリロードされず、サーバーへのリクエストは消えず、クライアント側のJavaScriptはリクエストされたページを何度もレンダリングします。 すべてが機能します!
中間結果
複数のページを追加し、クライアントとサーバーのルーティングを正常に構成して、すべてのシナリオで正しく機能することを確認しました。
3.フラックスとリダックス
まず、 FluxとReduxについて話すことができる限り練習できるように、「カウンター」を含むページを実装します。
Counter.jsxとStateCounter.jsxの 2つの新しいコンポーネントを作成しましょう。
カウンターは、渡された値と、この値の変更を担当するプラスボタンを表示します。
StateCounter - Counterコンポーネントの親コンポーネント。 現在のカウンター値を独自の状態ストレージに保存し、プラスボタンをクリックしたときにこの値を更新するためのビジネスロジックが含まれます。
インターフェイスとビジネスロジックを明示的に分離するために、このような実装を意図的に選択しました。
このようなコードは簡単なので、この手法は実際に非常によく使用されます。
- サポートと同行する
- テストする
- 再利用。
特に、このプロジェクトでは、複数のコンポーネントが一度にCounterを使用します。
src / components / CounterPage / Counter.jsx
import React, { Component, PropTypes } from 'react'; import Button from 'react-bootstrap/lib/Button'; import './Counter.css'; const propTypes = { onClick: PropTypes.func, value: PropTypes.number }; const defaultProps = { onClick: () => {}, value: 0 }; class Counter extends Component { render() { const { onClick, value } = this.props; return ( <div> <div className='counter-label'> Value: {value} </div> <Button onClick={onClick}>+</Button> </div> ); } } Counter.propTypes = propTypes; Counter.defaultProps = defaultProps; export default Counter;
src / components / CounterPage / Counter.css
.counter-label { display: inline-block; margin-right: 20px; }
src / components / CounterPage / StateCounter.jsx
import React, { Component } from 'react'; import Counter from './Counter'; class StateCounter extends Component { constructor() { super(); this.handleClick = this.handleClick.bind(this); this.state = { value: 0 }; } handleClick() { this.setState({ value: this.state.value + 1 }); } render() { return <Counter value={this.state.value} onClick={this.handleClick} />; } } export default StateCounter;
src / components / CounterPage / CounterPage.jsx
+++ import PageHeader from 'react-bootstrap/lib/PageHeader'; +++ import StateCounter from './StateCounter'; render() { --- return <div> </div>; +++ return ( +++ <div> +++ <PageHeader>Counters</PageHeader> +++ <h3>State Counter</h3> +++ <StateCounter /> +++ </div> +++ ); }
更新されたコードをテストします。 ブラウザで、「カウンタ」タブに移動し、「+」ボタンをクリックします。 値が0から1に変更されました。 次に、他のタブに移動してから戻ります。 カウンタ値は再び「0」になりました。 これは非常に期待されていますが、常に私たちが見たいと思うものではありません。
Fluxの概念について説明します。
注: Fluxは概念であり、ライブラリではありません。 今日、それを実装する多くの異なるライブラリがあります。
3.1。 フラックスフィンガーチップ
コンポーネントにはビジネスロジックは含まれませんが、インターフェイスのレンダリングのみを担当します。
アプリケーションには、アプリケーション全体の状態を保存するオブジェクトが1つあります。 これを「グローバル状態」と呼びますが、これが最も成功した用語であるかどうかは完全にはわかりません。 開発者の要求により、一部のコンポーネントは、関心のあるグローバル状態の一部に「サブスクライブ」します。 時間が経つにつれて、グローバル状態は変化する可能性があり、それにサブスクライブしているすべてのコンポーネントは自動的に更新を受信します。
- コンポーネント内のグローバル状態を明示的に変更することは禁止されています。 グローバル状態を変更するために、コンポーネントは特別なディスパッチ関数を呼び出します。 この関数の進行状況を追跡することは、最初のフラックスの原則に違反するため、アンチパターンです。 実際には、グローバル状態には、コンポーネントに必要なすべての情報、たとえばAPIリクエストの実行ステータスやエラーが含まれます。 この情報およびその他の情報は、 propsを使用してタイムリーかつ明示的にコンポーネントに送信されます。
重要な注意:グローバル状態は、 フロントエンドアプリケーションの状態のみを別のタブに記述し、ブラウザーのRAMに排他的に保存されます。 したがって、ユーザーがF5を押すと失われますが、これは設計上予想される、まったく正常な動作です。 このトピックについては、第3部で詳しく説明します。
実用例
オンラインストアのウェブサイトがあるとします:ページの中央に、ナビゲーションパネルに製品のリストが表示されます-製品の数とその合計値が表示されたバスケットアイコン、右側のどこかに-バスケットに追加された商品の詳細が表示されたブロック 一言で言えば、かなり一般的なシナリオです。
ユーザー視点シナリオ
- ユーザーは「カートに追加」ボタンをクリックします。
- ボタンがアクティブでなくなり、アイコンがロードインジケータに変わります。
- サーバーAPI要求が進行中です。
- サーバーパーツがリクエストを正常に処理して回答を返した後、「アイテムがカートに正常に追加されました」というプロンプトが上部に表示され、製品数のアイコンの値が1つ増え、金額が再計算され、カートの内容の詳細を含む新しいレコードがブロックに表示されます。 ダウンロードインジケータが消え、ボタン自体が再びアクティブになります。
- 手順3でエラーが発生した場合、ユーザーにエラーメッセージを表示し、バスケットに製品を追加するためのボタンを元の状態に戻します。
このスクリプトをjQueryで記述した場合、DOMを操作するために多くのコードを記述する必要があります。 すべての新しい顧客要件を実装する過程で、コードはより複雑になり、高い確率で何かが最終的に故障し、サポートの複雑さとコストは時間とともに新しい「ウィッシュリスト」とともに絶えず増加します。
フラックスの観点から同じシナリオ
注:カートに追加、通知、カート、およびカートの詳細コンポーネントは、グローバル状態にサブスクライブされます。
- ユーザーが「カートに追加」ボタンをクリックすると、 ディスパッチ関数が呼び出されます。
- この関数はグローバル状態を更新します。Addto Cartボタンはtrueに等しい新しいプロップの
loading
値を受け取り、それをオフにし、アイコンがこのコンポーネントのソースコードに従ってロードインジケーターに変わります。 - 次のステップ、関数はAPIにリクエストを行い、追加された製品に関する情報をバックエンドに保存します。
- 成功した場合、グローバル状態を更新します。通知コンポーネントは新しいprop
message
値を受け取り、ユーザーに表示します。Cartコンポーネントは新しい数量のpropcount
値を受け取り、注文金額のpropvalue
を受け取ります。CartDetailコンポーネントは値を受け取ります小道具items
-カートに追加されたすべてのアイテムに一致するアイテムの更新リスト。 将来、お客様がページ上で何か他のことをしたい場合、他のコンポーネントのコードやビジネスロジックを実行する機能を変更することなく、簡単に実現できます。 新しいコンポーネントを実装するだけで、そこにグローバル状態のどの部分に関心があるかを示すことができます。 - APIがエラーを返した場合、通知コンポーネントは対応するprop
message
値を受信し、情報メッセージをユーザーに表示します。 - この関数は、「カートに追加」ボタンにfalseに等しいプロップ
loading
新しい値を通知することにより、最後にグローバル状態を更新します 。 ボタンは元の状態に戻ります。
このような関数のサンプルコード
function addItemToCart(itemId) { return (dispatch) => { dispatch(addItemToCartStarted(itemId)); addItemToCartAPICall(itemId) .then( (data) => { dispatch(itemToCardAdded(data)); dispatch(addItemToCartFinished(data)); } ) .catch(error => dispatch(addItemToCartFailed(itemId, error))); } }
単純化されていますが、完全に正しいわけではありません。この例では、 ディスパッチ関数がグローバル状態の更新を担当しています。 , - , dispatch .
.
- , -,
<Button onClick={() => dispatch(addItemToCart(3))} />
.
- , , props .
!
3.2。 Redux
: Flux .
長所:
短所:
- : , .
:
- ;
- - ;
- redux ;
- redux ;
- redux ;
- "", ;
- ;
- .
: : " ?" " ?". ! , . , . , - , ! , , , , !
3.2.1. redux , react-redux redux-thunk
npm i --save redux react-redux redux-thunk
3.2.2. -
3.2.2.1 src/redux , src/redux/actions src/redux/reducers .
3.2.2.2 counterActions.js . , .
src/redux/actions/counterActions.js
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER'; export function incrementCounter() { return { type: INCREMENT_COUNTER }; }
3.2.2.3 counterReducer.js . .
src/redux/reducers/counterReducer.js
import { INCREMENT_COUNTER } from 'redux/actions/counterActions'; const initialState = { value: 0 }; export default function(state = initialState, action) { switch (action.type) { case INCREMENT_COUNTER: return { value: state.value + 1 }; default: return state; } }
このコードは何をしますか?
- , 0.
- INCREMENT_COUNTER , value 1.
- — .
: , redux ( "") @@INIT , .
3.2.3 redux
configureStore.js
src/redux/configureStore
import { applyMiddleware, combineReducers, createStore } from 'redux'; import thunk from 'redux-thunk'; import counterReducer from './reducers/counterReducer'; export default function (initialState = {}) { const rootReducer = combineReducers({ counter: counterReducer }); return createStore(rootReducer, initialState, applyMiddleware(thunk)); }
このコードは何をしますか?
- combineReducers , . , , -.
- . .
- thunk middleware . "" — , dispatch . , .
- createStore , "" .
3.2.4. redux
src/server.js
+++ import { Provider } from 'react-redux'; +++ import configureStore from './redux/configureStore'; app.use((req, res) => { +++ const store = configureStore(); ... --- const componentHTML = ReactDom.renderToString(<RouterContext {...renderProps} />); +++ const componentHTML = ReactDom.renderToString( +++ <Provider store={store}> +++ <RouterContext {...renderProps} /> +++ </Provider> +++ );
— props . props , child child . , , , , API , , .
, , . API , , , .
Provider react-redux store. , , , .
3.2.5. redux
src/client.js
+++ import { Provider } from 'react-redux'; +++ import configureStore from './redux/configureStore'; +++ const store = configureStore(); const component = ( +++ <Provider store={store}> <Router history={browserHistory}> {routes} </Router> +++ </Provider> );
3.2.6. "",
src/components/CounterPage/ReduxCounter.jsx
import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import Counter from './Counter'; import { incrementCounter } from 'redux/actions/counterActions'; const propTypes = { dispatch: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; class ReduxCounter extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { this.props.dispatch(incrementCounter()); } render() { return <Counter value={this.props.value} onClick={this.handleClick} />; } } ReduxCounter.propTypes = propTypes; function mapStateToProps(state) { const { value } = state.counter; return { value }; } export default connect(mapStateToProps)(ReduxCounter);
connect . :
- mapStateToProps , , "" , ;
- dispatch ;
- , mapStateToProps "" props ReduxCounter .
: connect High Order Components HOCs .
High Order Component : , , , , , props .
connect .
function connect(mapStateToProps) { function dispatch(...) { ... } const injectedProps = mapStateToProps(globalState); return (WrappedComponent) => { class HighOrderComponent extends Component { render() { <WrappedComponent {...this.props} {...injectedProps} dispatch={dispatch} />; } }; return HighOrderComponent; } }
, export default connect(mapStateToProps)(ReduxCounter) ?
- connect mapStateToProps .
- connect mapStateToProps , . { value: "_____"} , injectedProps .
- connect .
- ReduxCounter .
- props , ( {...this.props} ), injectedProps dispatch
3.2.7. CounterPage
src/components/CounterPage/CounterPage.jsx
+++ import ReduxCounter from './ReduxCounter'; <StateCounter /> +++ <h3>Redux Counter</h3> +++ <ReduxCounter />
3.2.8. テスト中
- "+" . .
- , . , . わあ! 動作します!
, , !
github — https://github.com/yury-dymov/habr-app/tree/v2 .
- .
- API.
- API.
Ps , , . 事前に感謝します!