eyecatch
Wed, Nov 7, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その10: Code Splitting、Flow、Jest、Enzyme

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はReact Routerをセットアップした。 今回は残りの要素をまとめてかたづける。 (adsbygoogle = window.adsbygoogle || []).push({}); Code Splitting webpackでリソースをバンドルすると、一回の通信でアプリの要素全てをロードできるので効率いいような気がするけど、アプリの規模が大きくなってくるとバンドルサイズが大きくなって、初期ロード時間が長くなり、つまり初期画面の表示に時間がかかるようになってしまう。 そもそも、いつもアプリの全画面をみるとは限らないので、いつもアプリの全要素をロードするのは無駄。 そんな問題に対応する技術がCode Splitting。 バンドルを分割し、(理想的には)必要な時に必要な分だけロードする技術。 Code Splittingのやりかたはいくつかあるが、webpackのディレクティブを使ったプリフェッチを、フォントモジュールに適用してみる。 src/index.jsx: import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { ConnectedRouter } from 'connected-react-router'; import App from './components/App'; import configureStore from './configureStore'; import configureStore, { history } from './configureStore'; -import './fonts.css'; +import(/* webpackPrefetch: true */ './fonts'); const store = configureStore(); const root = document.getElementById('root'); if (root) { ReactDOM.render( <Provider store={store}> <ConnectedRouter history={history}> <App /> </ConnectedRouter> </Provider>, root, ); } コード変更はこれだけ。 import()はダイナミックインポートという、ECMAScriptで現在策定中の機能。 これを使えるようにするためには、Babelのプラグインを追加する必要がある。 yarn add -D babel-plugin-syntax-dynamic-import .babelrc { "presets": ["env", "react"], - "plugins": ["styled-components"] + "plugins": ["styled-components", "syntax-dynamic-import"] } ダイナミックインポートの設定も完了。 これでフォントモジュールはメインのバンドルとは別ファイルになり、初期画面の表示時にはロードされず、ブラウザの空き時間に非同期にロードされるようになる。 Code SplittingはReactのドキュメントでも紹介されていて、そこにはReact特有のやり方も載っている。 React.lazyとSuspenseを使うものがかなりナウい。 Flow Reactに限らない話だけど、JavaScriptは動的型付け言語なので、特に規模が大き目なアプリを開発するとなると保守性が悪くなりがちで辛い。 ので、できれば静的型付けでやりたい。 JavaScriptを静的型付けにするには、TypeScriptとFlowという二つの選択肢がある。 今回、FlowがReactと同じくFacebook製なので、Reactと相性がいいかと思ってFlowを選択したけど、人気やエコシステムの充実度から見るとTypeScriptのほうがよかった気がする。 ので、Flowについてはさらっと書く。 Flow導入 Flowは、ソースに型情報を付けて静的型チェック可能にしつつ、実行時には型情報を取り去って普通のJavaScriptとして実行できるようにする仕組み。 型チェックするツールはflow-binパッケージで配布されていて、型情報の除去はbabel-preset-flowを使ってBabelでできる。 babel-preset-flowは、すでにインストールしたbabel-preset-reactに含まれてるので、敢えて入れる必要はない。 yarn add -D flow-bin これで、yarn flowでFlowを実行できるようになった。 $ yarn flow version yarn run v1.7.0 $ C:\Users\kaitoy\Desktop\bin\pleiades\workspace\react-redux-scaffold\node_modules\.bin\flow version Flow, a static type checker for JavaScript, version 0.77.0 Done in 0.38s.
eyecatch
Fri, Nov 2, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その9: React Router

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はRedux Sagaをセットアップした。 (adsbygoogle = window.adsbygoogle || []).push({}); フロントエンドのルーティング Webアプリケーションにおけるルーティングとは、クライアントがリクエストしたURLに対して、返すべきリソースを選択する処理。 昔はバックエンド(i.e. サーバサイド)でやってたけど、バックエンドでリソースを返すということは、ページ遷移が発生するということなので、ネイティブアプリケーションに比べてUXが落ちてしまう。 一方、ページ遷移を発生させないようにAjaxでサーバとやりとりしつつ、ちまちまDOMをいじるのは大変。 DOMをごっそり書き換えて、ページ遷移なしに画面を切り替えることはできるけど、ナイーブにやると以下のような問題がある。 URLと画面の紐づけがなく、URLを指定して直接開けない ブラウザの進む、戻るが使えない 宣言的に書けない こういった問題に対応するため、フロントエンドでのルーティング技術が生まれた。 フロントエンドのルーティングでは、URLが変わってもリクエストはサーバに飛ばない。 代わりに、フロントエンドフレームワークがそのURLを見て、適切な画面を選んでレンダリングする。 ハッシュベースのルーティング URLが変わってもリクエストがサーバに飛ばないとは何事か。 それを実現するやりかたは2通りある。 古くはハッシュ(#、フラグメント識別子)をつかったやり方。 例えば、http://example.com/でUIをサーブしているとすると、http://example.com/#fooとか、http://example.com/#barで別々のページの状態を表現する。 ハッシュ以降が変わってもブラウザがサーバにリクエストを投げることはないので、クライアント側でハンドリングできる。 (因みに、ハッシュを含んだURLをブラウザのアドレスバーに入れても、ハッシュを除いたURLでリクエストが送られる。この挙動の根拠となる規格はRFCなどを調べても見つからなかったけど…) ハッシュの書き換えは、JavaScriptで以下のようにしてできる。 location.hash = newHash; こういう処理を、例えばWeb UIのボタンをクリックしたときなんかに実行してURLを変えて、その上で画面を更新してやればいい。 そのあと、ブラウザの戻るボタンなんかを押されると書き換える前のURLにもどるわけだけど、これを検知するためにsetInterval()とかで定期的にlocation.hashを監視してたりした。 History APIによるルーティング ハッシュベースのルーティングは見るからにしょぼい。 URLのハッシュ以降しか使えないのもしょぼいし、内部の処理も泥臭い。 これが、HTML 5でHistory APIがでて変わった。 History APIはJavaScriptのAPIで、ブラウザの履歴を操作できる。 const state = { hoge: "hogeee" }; history.pushState(state, "", "/foo/bar"); こんな感じのを実行すると、URLが/foo/barに変わる。(が、もちろんサーバにはリクエストは飛ばない。) で、ブラウザの戻るボタンを押すと、popstateイベントが発生するので、それにイベントハンドラを登録しておけば、もとのURLに戻った時にも適時画面を書き換えられる。 popstateイベントからは、pushState()に渡したstateオブジェクトを取得できる。 ところで、ブラウザのアドレスバーに/foo/barを直打ちするとどうなるかというと、普通にWebサーバを設定しておくと、/foo/bar/index.htmlを返そうとして、無いので404エラーになっちゃう。 ので、サーバ設定では、どのURLも同じリソース(e.g. /index.html)をしといて、そこからJavaScriptを呼んで、URLを読み取って、画面を描いてやればいい。 HTML 5が普及するにつれ、このようなHistory APIを使ったフロントエンドルーティングをするフレームワークやライブラリが色々出てきた。んだろうと思う。 React Router Reactのエコシステムとしては、React Routerがフロントエンドルーティングを実現してくれる。 React Routerは、宣言的にフロントエンドルーティングを実現できるReactコンポーネントのライブラリ。 Reduxとともに使う場合は、Connected
eyecatch
Sun, Oct 7, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その8: Redux-Saga

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はReact Reduxをセットアップした。 (adsbygoogle = window.adsbygoogle || []).push({}); ReduxのMiddleware Redux単体では同期的なデータフローしか実装できない。 つまり、Actionを発生させたら、即座にディスパッチされ、stateが更新される。 一方、非同期なフローとは、REST APIを呼んでその結果でstateを更新するような処理。 REST API呼び出しが非同期なわけだが、これをReduxのピュアなフローのどこで実行するのかというと、Middlewareで実行する。 MiddlewareはStoreのdispatch()をラップして、Actionをトラップして副作用を含む任意の処理をするための機能。 Middlewareの仕組みについてはこの記事が分かりやすい。 Middlewareには例えば、発生したActionの内容と、それによるstateの変化をログに出力するredux-loggerがある。 デバッグに有用そうなので入れておく。 yarn add redux-logger v3.0.6が入った。 Middlewareは、ReduxのapplyMiddleware()というAPIを使って、createStore()実行時に適用できる。 src/configureStore.js: -import { createStore } from 'redux'; +import { createStore, applyMiddleware } from 'redux'; +import { logger } from 'redux-logger'; import rootReducer from './reducers/rootReducer'; export default function configureStore(initialState = {}) { + const middlewares = []; + if (process.env.NODE_ENV === `development`) { + middlewares.push(logger); +
eyecatch
Mon, Oct 1, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その7: React Redux

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はReduxをセットアップした。 (adsbygoogle = window.adsbygoogle || []).push({}); React Redux 前回はReduxをセットアップして、ActionをStoreにディスパッチしてstateを更新できるようになった。 今回はこれをReactにつなぐ。 使うのはReact Redux。 yarn add react-redux v5.0.7が入った。 Presentational Components と Container Components React Reduxの使い方を理解するには、Presentational Components と Container Components という概念を知らないといけない。 これはReactコンポーネントを役割別に分ける考え方で、それぞれ以下のような特徴をもつ。 Presentational Components Container Components 主な役割 DOMをレンダリングする データを取得したりstateを更新したりする(Reduxとつなぐ) Reduxとの関連 無し 有り データの読み込み propsから読む Reduxのstateオブジェクトから読む データの更新 propsで渡されたコールバックを呼ぶ ReduxのActionをディスパッチする 作り方 自前で書く React Reduxで生成する 要するに、普通にReactで作ったUIコンポーネントを、React Reduxで生成するContainer ComponentでラップしてやることでReduxのStoreとつなぐことができる。 connect() Container Componentの生成にはReact Reduxのconnect()というAPIを使う。 React Reduxを使う場合、Reduxのstateの更新に応じてReactコンポーネントに新しいpropsを渡して再レンダリングすることになるが、この新しいpropsを作ってコンポーネントに渡す処理を定義するのがconnect()。 connect()の第一引数には、ReduxのstateのプロパティとReactコンポーネントのpropsのプロパティとのマッピングをする関数であるmapStateToProps()を渡す。 mapStateToProps()はstateの更新に応じて呼び出され、引数にstate(と現在のprops)が渡される。 mapStateToProps()が返すオブジェクトはReactコンポーネントに渡されるpropsにマージされる。 connect()の第二引数には、Storeのdispatch()を呼び出す処理とReactコンポーネントのpropsのプロパティとのマッピングをする関数であるmapDispatchToProps()を渡す。 mapDispatchToProps()の引数にはdispatch()が渡される。 mapDispatchToProps()が返すオブジェクトはReactコンポーネントに渡されるpropsにマージされる。 (mapDispatchToProps()は第二引数にpropsを受け取ることもできて、この場合、propsの更新に反応して呼び出されるコールバックになる。) connect()を実行すると関数が返ってくる。 この関数にReactコンポーネント(Presentational Component)を渡して実行すると、Storeに接続されたReactコンポーネント(Container