react-to-printを使ってReact.jsでDOMを印刷する

## はじめに

みなさん印刷してますか？
私は印刷していません。
印刷しようがしまいが、エンドユーザからは印刷機能を求められるものです。
今日は電子データ化、電子署名など一切を忘れましょう。

さて、帳票を印刷する際に使うツールとしてまず思いつくのはPDF、Excelでしょうか。
しかし、システムをユーザへ提供するし易さ、スピードで最も良いインターフェースはWebアプリケーションです。
というわけで `React.js` で帳票を印刷します。

## Webアプリケーションで印刷するということ

Webアプリケーションで印刷を行う際に真っ先に思いつくのは、BackEndで印刷用ファイル(PDF)を生成し、FrontEndはそれをダウンロード・PDFViewerで印刷といった流れでしょうか。
今回はこれは行いません。

一方で、Webブラウザにはそれ独自の印刷機能が付いています。
この印刷機能はWebページ＝DOMを直接印刷してくれるようです。

というわけで DOMを印刷します。

イメージ：↓の「3. FrontEndで帳票作成」
![print_cases.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398666/ad19a2db-b105-47d8-df7b-35df66907150.png)


## つかうもの

 - React.js 16.9.0
 - [react-to-print](https://www.npmjs.com/package/react-to-print) 2.4.0

**react-to-print** を選択した理由としては、SampleCodeで Class Component/Functional Component の両者に言及されていたからです。
React Hook :heart_exclamation:  

その他、簡易的なデザイン付けのため、いつもお世話になっている [material-ui](https://material-ui.com/)を使用します。

## 成果物

 - [ソースコード](https://github.com/a-terarara/react-to-print-practice)
 - [Demo](https://a-terarara.github.io/react-to-print-practice/)

## Demoについて
![print_demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398666/2e667506-1556-0bc7-5710-4ee5c9ffaa94.png)

プロフィールカードに「プレビュー」と「印刷」ボタンが表示されています。

- プレビュー：プレビュー画面を出してから印刷出来ます
- 印刷：直接印刷画面を出します。

印刷画面はブラウザの機能に依存します。

## 概要

### プレビュー画面

```js

        <DialogTitle id="alert-dialog-title">{`${src.name}のプロフィールを印刷しますか？`}</DialogTitle>
        <DialogContent>
          <Print ref={componentRef} src={src} />
        </DialogContent>
        <DialogActions>
          <Button onClick={handleClose} color="primary">
            キャンセル
          </Button>
          <ReactToPrint
            trigger={() => (
              <Button size="small" color="primary">
                印刷
              </Button>
            )}
            content={() => componentRef.current}
          />
        </DialogActions>
```

プレビュー画面はDialogで表示しています。
印刷対象のComponentは `Print` で定義しています。

```js
          <Print ref={componentRef} src={src} />
```

印刷機能を提供してくれるのは `ReactToPrint` です。

```js

          <ReactToPrint
            trigger={() => (
              <Button size="small" color="primary">
                印刷
              </Button>
            )}
            content={() => componentRef.current}
          />
```

`ReactToPrint` のcontentに先ほどの `Print` を渡します。
その際、`componentRef` を経由していますが、これは[`useRef()`](https://ja.reactjs.org/docs/hooks-reference.html#useref)でrefオブジェクトを渡しています。

![preview_demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398666/5610ebfa-3a7e-b23d-659f-2dcb283c14b7.png)


### 印刷画面

```js
      <CardActions>
        <ReactToPrint
          trigger={() => (
            <Button size="small" color="primary">
              印刷
            </Button>
          )}
          content={() => componentRef.current}
        />
        <div style={{ display: "none" }}>
          <Print ref={componentRef} src={src} />
        </div>
      </CardActions>
```

直接印刷する際には`Print` を表示する必要がないので`display: "none"`で非表示にしています。

```js
        <div style={{ display: "none" }}>
          <Print ref={componentRef} src={src} />
        </div>
```

chromeだとこうでます。
![print_view_demo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398666/35d73d99-6e28-1bfd-3486-daa1a2d625bf.png)

## メリット

 - 画面構成と印刷内容構成を同時に実装できる  
 - CSSでデザインできる
 - なんかおしゃれな気がする
 - BackEnd処理が軽くなる

## デメリット

 - 印刷設定ができない(？)  
 ※今回使用したreact-to-print単体ではできませんが、CSSを駆使すれば出来るかも :thinking: 


## 【補足】実はプレビューしないとCSSの反映が完璧ではない

今回、`display: "none"`を使用してプレビューを出さずに印刷する方法を提示しましたが、そうするとCSSが想定とずれることがあります。

|プレビューしてから印刷|直接印刷|
|:--|:--|
|![print_preview_ver.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398666/4009177e-d83c-3c54-f1cf-c9e23e631476.png)|![print_direct_ver.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398666/c4f1c773-ae51-f908-1b43-8e0778626084.png)|

上図では氏名とoutlineが重なっています。

\# ただ、プレビューせずに印刷することは力技でやっているので自業自得感はあります :cry: 

## おわりに

React.jsに印刷機能を持たせてみました。
自作の職務経歴書くらいはかんたんに作れそうですね :grinning: 
みなさんも良き印刷ライフを