2018年8月10日

ReactでCSVファイルを読み込んでクライアント側で帳票を出力する

やりたかった事


あるWebサービスからCSV形式でデータをエクスポート出来るのですが、そのデータからちょっとした帳票を印刷したいという要望がありました。

そこでなるべく手っ取り早く、ブラウザだけで動くものが出来ないかと考えて作ってみたのが今回のプログラムです。

大まかな流れは、
  1. ローカルフォルダにあるCSVファイルを指定するとブラウザ上のJavaScriptでその内容を読み込む。
  2. 読み込んだ内容からHTMLで帳票を生成して表示する。
  3. ブラウザの印刷機能を使って手動で印刷またはPDFとして保存する。
という感じになります。


CSVファイルの例







作成したい帳票


実際に動いているもの

https://mikehibm.github.io/react-csv-example/




1. プロジェクトを作成する


プロジェクトは前回のエントリーと同様に Create React App + TypeScriptで作ることにします。
npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start

FileSelect.tsx と Report.tsx というファイルを新規作成して、App.tsx からこれらを呼び出して使うようにします。

最初は FileSelect コンポーネントを表示してCSVファイルの選択を行い、ボタンがクリックされたらファイルの内容を読み取って、Report コンポーネントに渡します。




Report コンポーネントが表示されている時は FileSelect コンポーネントは非表示になるようにします。




2. JavaScriptでCSVファイルの内容を読み込む


HTML5のFile APIを使います。FileReaderでの読み込みは非同期処理なので、処理をラップしてPromiseを返す関数を作りました。







3. ファイルの内容をパースしてオブジェクトの配列に変換する


上の readFileAsText() からファイル全体の内容が文字列として返ってくるので、それをパースしてオブジェクトの配列に変換します。

具体的には、まず改行文字で区切って行単位の配列に分け、さらにその各行について「,」で区切って列単位の配列に分けます。つまり最終的には string[][] (文字列の配列の配列)になります。




ここでは簡単に改行文字(\n)と「,」で区切っているだけですが、実際には改行文字が違う、値に「,」が含まれている、などさまざまなケースがあり得るのでCSVのパース処理はちゃんとやろうとすると実は結構大変です。

なので必要であれば下の記事で紹介されている csv-parser などを使った方が良いかもしれません。

ブラウザ上でCSVファイルをパースする
https://qiita.com/ledsun/items/e38ee0dff8f26bf8d930




その後、文字列の配列(の配列)から、今度は帳票を生成する時に扱いやすいように、WorkItemというクラスのインスタンスの配列に変換します。

この時に、文字列型から日付型への変換や、CSVファイルに無い項目の値(duration)の計算なども同時に行っています。





4. HTMLで帳票を生成する


WorkItemオブジェクトの配列を受け取って、tableタグで表形式のHTMLに変換します。

本当は帳票のタイトル、見出し、明細行などをそれぞれ別のコンポーネントに分けた方が良いのだと思いますが、今回はあまり時間がなかったので1コンポーネントで作ってしまいました。





あとは画面に表示された帳票をブラウザの印刷機能で印刷するだけです。デフォルトではおそらく余計なヘッダーやフッター(ページタイトルやURL)も印刷されてしまいますが、印刷時の設定画面でこれらをオフにしておけばきれいに出力されるはずです。


今回作ったアプリの全ソースコードは下記にあります。

https://github.com/mikehibm/react-csv-example







2018年8月8日

Create React App + TypeScript で最初につまずいたこと

ちょっとしたサンプルプログラム的なものを Create React App + TypeScript で作ろうと思ったのですが、思わぬところでいきなり詰まってしまいました。


Create React App(以下CRA)で TypeScript と言えば、

https://github.com/wmonk/create-react-app-typescript

が定番だと思います。


さて、下の通りにプロジェクトを作成して実行しました。

npm install -g create-react-app
create-react-app my-app --scripts-version=react-scripts-ts
cd my-app/
npm start


もちろんここまでは全く問題なし。早速VS Codeでプロジェクトのフォルダを開いて、アプリケーションを作り始めました。



ところが、あるタイミングでふと

console.log("hogehoge");

を入れて実行してみようと思ったところ、いきなりコンパイルエラーになってしまいました。


「Calls to 'console.log' are not allowed.」というエラーですが、これにはちょっと驚かされました。console.logを入れただけでアプリケーションが動かなくなるなんて。。。


これですが、デフォルトで設定されているTSLintのルールがめちゃくちゃ厳格なものになっているのが原因なようです。


厳格すぎてこれでは使いづらいという声も多く、GitHubのIssuesでも議論になっているようですね。


オリジナルのCRA作者のDan Abramov氏からも、「この厳しいルールはCRAを作った時の思想(初心者でもすぐに動くものを作り始められる。本当にクリティカルなもの以外はワーニングのみでエラーにはしない。)と合わないから、ゆるくした方が良いんじゃない?」 という趣旨のIssueをわざわざ上げられているぐらいです。


create-react-app-typescript リポジトリ作者のWill Monk氏によると、「最初は自分が職場で使っているルールをコピーして使っていたのだけれど、後で一般的に推奨されているプリセット("tslint:recommended", "tslint-react"など)を使うように変更した」との事。「それでもまだルールが厳しすぎるなら、誰かがオリジナルのCRAと同等のゆるいルールの設定を作ってPRしてくれたら喜んでそれをマージするよ。」とも言っているので、彼自身は特にどうしても厳しいルールを他の人に強制する気は無いようです。


その後このIssue上で議論は進んでいるようですが、このブログ執筆時点の最新版ではまだルールの変更には至っていないようです。


さて、なにはともあれとりあえず console.log を使えるようにする方法ですが、上のIssueでも言及されているのですが、Microsoftによる下記のページにわかりやすくはっきりと記載されていました。

https://github.com/Microsoft/TypeScript-React-Starter


プロジェクトのルートフォルダにある tslint.json ファイルを下のように書き換えればOKです。(赤字を削除、青字を追加。)

{
-  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
+  "extends": [],
+  "defaultSeverity": "warning",

"linterOptions": {
     "exclude": [
       "config/**/*.js",
       "node_modules/**/*.ts"
     ]
   }
 }

これで問題なく開発が続けられるようになりました!