eyecatch
Mon, Nov 26, 2018

React + Reduxアプリケーションプロジェクトのテンプレートを作る ― その11: FlowからTypeScriptへ移行

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はCode Splitting、Flow、Jest、Enzymeをセットアップした。 前回でこのシリーズを終わりにするつもりだったけど、型システムをFlowからTypeScriptに移行したのでそれについて書く。 (adsbygoogle = window.adsbygoogle || []).push({}); TypeScript TypeScriptはMicrosoft製のAltJS。 もともとはCoffeeScriptのように言語の機能面(e.g. class構文やアロー関数)を補強しつつ、静的型付けをサポートする言語だったが、最近はECMAScriptが前者をカバーしてるので、後者を主な目的として使う人が多い。 2012年に誕生した言語で、同様に静的型付けをサポートするFlowよりも2歳ほど年上。 TypeScript vs Flow 個人的には、静的型付けだけを目的にするならAltJSである必要はなく、静的型付けだけを補完するFlowのほうが筋がいいような気がする。 TypeScriptはECMAScriptの進化に追従すべく、追加される機能や構文をサポートするためのエンハンスを繰り返しているが、そこはBabelに任せて静的型付けに注力したらいいような。 とはいえ、以下のような点を鑑み、結局TypeScriptを選択した。 TypeScriptの方が人気 GitHubのプロジェクトのスター数はTypeScriptが4万超えでFlowが2万弱。 観測している限り、FlowからTypeScriptへ移行したというのは聞くが、逆は聞かない。 人気があるということはコミュニティやエコシステムが大きいということ。 TypeScriptがノってる BabelやCreate React AppがTypeScriptをサポートして来ていて、なんだか時流にのっている。 Flowは型定義ファイルの管理方法が微妙 Flowはflow-typedという専用のツールを使ってファイルをダウンロードし、ダウンロードしたものをGitとかのVCSでバージョン管理するというやりかた。 TypeScriptはnpmで管理されてるので、Yarnでダウンロードもバージョン管理もできる。VCSのリポジトリに自前のコードしか入れないで済むのもいい。 TypeScriptの方が型定義ファイルが沢山提供されてる Flowの10倍くらいある。 TypeScriptの方がエラーメッセージが分かりやすい というのをどこかで聞いた。 Flowの方が段階的に型を導入できる、というのは昔の話 今はTypeScriptもオプションによって段階的に導入できるというのが定評。 そもそも最初から型付けするならどうでもいい。 Flowの方が厳密な型チェックしてくれる、というのも昔の話 TypeScriptが追い付いてきて、今はほぼ同程度らしい。 TypeScript+VSCodeの開発体験が最高すぎるらしい どっちもMicrosoft製なので。 TypeScriptの方がドキュメントが充実してる TypeScriptの方が、いざというときにソースが読みやすい TypeScriptはTypeScriptで実装されてて、FlowはOCamlで実装されてる。 参考: https://github.com/niieani/typescript-vs-flowtype https://texta.pixta.jp/entry/2018/06/07/120000 https://narinymous.hatenablog.com/entry/2018/03/02/032130 https://base.terrasky.co.jp/articles/zuUtT 前回の記事ではFlowを導入したんだけどTypeScriptに移行する羽目に。 FlowとTypeScriptとで型の表現方式や表現力にあまり差はなかったのでそこはまあ手間ではなかったんだけど、以下のような問題に対応する必要があった。 ビルド時にTypeScriptの方が時間がかかる。 TypeScriptのリンタであるTSLintが、FlowのESLintよりルールが貧弱 TypeScriptのコンパイラがチェックしてくれるからいいのかもしれないけど。 TypeScriptはAltJSなので、何かと連携するときに何かと面倒 Jestでユニットテストするときはどうするんだっけとか プレーンJavaScriptと混在した環境ではTSLintとESLint併用しなければいけないんだっけとか FlowからTypeScriptへの移行 脱Flow とりあえずFlowを取り除く。 $ yarn remove flow-bin flow-typed @babel/preset-flow eslint-plugin-flowtype babel-eslint $ rm -f .flowconfig .babelrc: { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], - "@babel/preset-flow", "@babel/preset-react" ], "plugins": ["styled-components", "@babel/plugin-syntax-dynamic-import"] } .eslintrc.js: module.exports = { env: { browser: true, 'jest/globals': true, }, - parser: 'babel-eslint', - extends: ['airbnb', 'plugin:flowtype/recommended', 'prettier'], - plugins: ['flowtype', 'jest'], + extends: ['airbnb', 'prettier'], + plugins: ['jest'], }; 環境はこれでよくて、あとは各.jsファイルと.jsxファイルから// @flowを消して、型情報も消す。 (型情報はTypeScriptでも同じようなのを書くので残しておいてもいい。) TypeScript導入 パッケージインストール 以下のパッケージを入れる。 typescript: TypeScript本体。コンパイラ(tsc)等を含む。 @types/*: 各3rdパーティライブラリの型定義ファイル(DefinitelyTyped)。(型定義はライブラリ本体のパッケージに含まれている場合もある。) awesome-typescript-loader: TypeScriptを処理するためのwebpackのローダ。他の選択肢としてts-loaderがあるが、公式のチュートリアルがawesome-typescript-loaderをメインで紹介してるのでこっちにする。 $ yarn add -D typescript @types/react @types/react-dom @types/react-redux @types/redux-logger @types/history @types/react-router-dom @types/uuid @types/styled-components awesome-typescript-loader TypeScriptはv3.1.6、awesome-typescript-loaderはv5.2.1が入った。 TypeScriptの設定 TypeScriptの設定ファイルであるtsconfig.jsonはtscコマンドでテンプレートを生成できる。 $ yarn tsc --init 生成されたファイルをプロジェクトルートに置いて、ちょっといじって以下の感じに。 (jsonなのにコメント書ける…) tsconfig.json: { "compilerOptions": { /* Basic Options */ "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'.
eyecatch
Wed, Nov 7, 2018

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

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はReact Routerをセットアップした。 今回は残りの要素をまとめてかたづける。 (2018/11/21更新) (adsbygoogle = window.adsbygoogle || []).push({}); Code Splitting webpackでリソースをバンドルすると、一回の通信でアプリの要素全てをロードできるので効率いいような気がするけど、アプリの規模が大きくなってくるとバンドルサイズが大きくなって、初期ロード時間が長くなり、つまり初期画面の表示に時間がかかるようになってしまう。 そもそも、いつもアプリの全画面をみるとは限らないので、いつもアプリの全要素をロードするのは無駄。 そんな問題に対応する技術がCode Splitting。 バンドルを分割し、(理想的には)必要な時に必要な分だけロードする技術。 Code Splittingのやりかたはいくつかあるが、ダイナミックインポートとReact.lazyとReact Suspenseとwebpackのプリフェッチディレクティブを使ったやつを、フォントモジュールに適用してみる。 src/components/App.jsx: -import React from 'react'; +import React, { Suspense } from 'react'; import { Route, Redirect } from 'react-router-dom'; import Home from './Home'; -import Fonts from '../fonts'; +const Fonts = React.lazy(() => import(/* webpackPrefetch: true */ '../fonts')); const App = () => ( <div> <Route exact path="/" render={() => <Redirect to="/home" />} /> <Route exact path="/home" component={Home} /> - <Fonts /> + <Suspense fallback={<div />}> + <Fonts /> + </Suspense> </div> ); export default App; コード変更はこれだけ。 import()がダイナミックインポートで、ECMAScriptで現在策定中の機能。 これを使えるようにするためには、Babelのプラグインを追加する必要がある。 yarn add -D @babel/plugin-syntax-dynamic-import .babelrc: { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], "@babel/preset-react" ], - "plugins": ["styled-components"] + "plugins": ["styled-components", "@babel/plugin-syntax-dynamic-import"] } ダイナミックインポートの設定も完了。 これでフォントモジュールはメインのバンドルとは別ファイルになり、初期画面の表示時にはロードされず、ブラウザの空き時間に非同期にロードされるようになる。 Flow Reactに限らない話だけど、JavaScriptは動的型付け言語なので、特に規模が大き目なアプリを開発するとなると保守性が悪くなりがちで辛い。 ので、できれば静的型付けでやりたい。 JavaScriptを静的型付けにするには、TypeScriptとFlowという二つの選択肢がある。 今回、FlowがReactと同じくFacebook製なので、Reactと相性がいいかと思ってFlowを選択したけど、人気やエコシステムの充実度から見るとTypeScriptのほうがよかった気がする。 ので、Flowについてはさらっと書く。 Flow導入 Flowは、ソースに型情報を付けて静的型チェック可能にしつつ、実行時には型情報を取り去って普通のJavaScriptとして実行できるようにする仕組み。 型チェックするツールはflow-binパッケージで配布されていて、型情報の除去は@babel/preset-flowを使ってBabelでできる。 yarn add -D flow-bin @babel/preset-flow .babelrc: { "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ], + "@babel/preset-flow", "@babel/preset-react" ], "plugins": ["styled-components", "@babel/plugin-syntax-dynamic-import"] } これで、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をセットアップした。 (2018/11/21更新) (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コンポーネントのライブラリ。
eyecatch
Sun, Oct 7, 2018

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

ReactとReduxを学ぶために、開発環境というかプロジェクトテンプレートをスクラッチから作っている。 (最終的な成果はGitHubに置いた。) 前回はReact Reduxをセットアップした。 (2018/11/21更新) (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);