<-- mermaid -->

React + TypeScriptでExcelファイルをダウンロードする機能を作る

あいさつ

はじめまして! AlphaDriveでエンジニアをしている、神と申します!

今回は、React + TypeScriptでExcelファイルをダウンロードする機能を作る方法についてご紹介したいと思います

この記事で伝えたいこと

  • React + TypeScriptの環境でExcelファイルをダウンロードする機能を作る方法
  • Sheet.jsの基本的な使い方

アウトプットのイメージ

今回はこのようなExcelファイルをダウンロードできるようにしたいと思います。

実装してみる

Excelファイルを作成するライブラリ

■ 各ライブラリ比較

ブラウザで動作するExcelファイルを操作できるライブラリの候補としては以下のようなものが上げられます。

Exceljs

Sheet.js(xlsx)

officegen

Sheet.js(xlsx) officegen Exceljs
メリット ・書き出し、読み込みなどの基本的な処理はできる
・海外の会社がメンテナンスをしている
・Excel以外にもパワポやワードのファイルも出力できる ・書き出し、読み込みなどの基本的な処理はできる
・スタイルの付与も可能
デメリット ・Sheet.js CommonEditionだと機能が制限される(有料版のProEditionだとかなり高度な処理を行える) ・メンテナンスが長期間されていない
・今後もメンテナンスされなさそう
・単純な出力ならできるが、読み込みとかはできなさそう
・メンテナンスが長期間されていない
・2023年4月ころから再びPRがマージされだしたが、いつまで続くかは疑問が残る

■ 選定したライブラリ

各ライブラリのメリデメを比較した結果、今回はSheet.js(xlsx)というライブラリを使用しようと思います! ドキュメントを参考にご自身の環境にインストールしてください。

https://github.com/SheetJS/sheetjs

選定理由としては、以下になります。

  • メンテナンスの観点では一番安定していそう(他のライブラリはあまり活発にメンテナンスされていない)
  • CommonEditionでも基本的なExcelファイルの書き出し、読み込みができる

実装方法

Excelファイル(ワークブック)オブジェクトを作る

ワークブックオブジェクトを作成するためにはシートオブジェクトが必要で、シートオブジェクトを作成するにはセルオブジェクトが必要になってきます(実際にExcelファイルを開いてみるとイメージしやすかと思います)。なのでまずはセルオブジェクトから作っていきます。

■セルオブジェクトの作り方

セルにはシートに表示させたいデータが入ります。なのでアウトプットのイメージで上げたデータを定義していきます。データは2次元配列で定義します(他の方法もありますが、今回はこの方法を採用します)。1つの配列が1つの行を表します。なので以下のようになります。

// 省略
const data = [
  ["魚種", "適正水温", "餌", "平均体長"],
  ["イワシ", "14~17度", "プランクトン", "20cm"],
  ["アジ", "19~23", "プランクトン・甲殻類など", "30cm"],
  ["サバ", "14~19度", "プランクトン・魚類など", "30cm"],
  ["シーバス", "15~18度", "雑食", "60cm"],
];

■シートオブジェクトの作り方

次に、先程作成したデータを元にシートオブジェクトを作成します。 APIを見ると、シートオブジェクトを作成する関数はいくつかあるようです。 https://docs.sheetjs.com/docs/api/utilities/

今回は二次元配列でデータを用意したので、utils.aoa_to_sheet という関数を使用します。

import { utils } from 'xlsx'

// 省略
const sheet = utils.aoa_to_sheet(data)

■ワークブックオブジェクトの作り方

最後にワークブックオブジェクトを作成します。book_new 関数でワークブックオブジェクトを作成し、book_append_sheet 関数でワークブックにシートを追加します。

import { utils } from 'xlsx'

// 省略
const book = utils.book_new()
const sheetName = '魚図鑑'
utils.book_append_sheet(book, sheet, sheetName)

作成したワークブックオブジェクトをダウンロードする

先程作成したワークブックオブジェクトをダウンロードできるようにします。

ファイルのダウンロード機能は今回は自前で実装します。 ファイルのダウンロードにもライブラリを使用しようと思ったのですが、FileSaverなどのライブラリはあまりメンテナンスされておらず、新しいライブラリも探してみたのですが、良さそうなものがなかったためです。

■ワークブックオブジェクトを書き出す

作成したワークブックオブジェクトをダウンロードするのに適した型にするため、書き出しを行います。書き出しについてはこちらが参考になります。 https://docs.sheetjs.com/docs/api/write-options/

// 省略
const binary: Unit8Array = write(book, { type: 'array' })
const blob = new Blob([binary], {
  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});

writeの第二引数のtypeは6種類の値を渡せます。渡す値によって戻り値が変わります。 https://docs.sheetjs.com/docs/api/write-options/#output-type

どの値を渡してもany型で返ってくるので、それが嫌な人は型を指定する必要があります。 ※ 渡す値によって戻り値の型を変えてほしいですよね…。自分で関数を作ってラップし、コンディショナルタイプをうまく使えば実現可能ですが…。

blobのコンストラクタの第二引数のtypeにはExcelのMIME Typeを渡します。 今回ダウンロードするファイルは.xlsxを想定しているため、上記のMIME Typeを渡しています。https://developer.mozilla.org/ja/docs/Web/API/Blob/type

■ダウンロードできるようにする

作成したblobをダウンロードします。

まずaタグを作成し、href属性には先程作成したblobから作ったURLを、download属性にファイル名を指定します。そしてclick関数を呼ぶことでダウンロードができます。

const fileName = "魚図鑑"
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', fileName);
link.click();
link.remove();
URL.revokeObjectURL(url);

ここまでのコードを関数にまとめて、その関数をbuttonのイベントハンドラーなどに渡すとExcelファイルがダウンロードできるようになります。 とても雑ですが、とりあえず全部つなげるとこうなります。

import { utils, write } from 'xlsx';

export const ExcelButton = () => {
    const downloadExcel = () => {
        const data = [
            ['魚種', '適正水温', '餌', '平均体長'],
            ['イワシ', '14~17度', 'プランクトン', '20cm'],
            ['アジ', '19~23', 'プランクトン・甲殻類など', '30cm'],
            ['サバ', '14~19度', 'プランクトン・魚類など', '30cm'],
            ['シーバス', '15~18度', '雑食', '60cm']
        ];

        const sheet = utils.aoa_to_sheet(data);
        const book = utils.book_new();
        const sheetName = 'Fish';
        utils.book_append_sheet(book, sheet, sheetName);
        const buffer: Uint8Array = write(book, { type: 'array' });
        const blob = new Blob([buffer], {
            type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        });
        const fileName = '魚図鑑';
        // saveAs(blob, fileName);
        const link = document.createElement('a');
        const url = URL.createObjectURL(blob);
        link.setAttribute('href', url);
        link.setAttribute('download', fileName);
        link.click();
        link.remove();
        URL.revokeObjectURL(url);
    };
    return <button onClick={downloadExcel}>Excelファイルダウンロード</button>;
};

このコンポーネントを組み込み、ボタンを押すことでExcelファイルのダウンロードができます!

小ネタ

行の高さを変更したいとき

sheetオブジェクトの!rowというプロパティに値を渡してあげる必要があります。https://docs.sheetjs.com/docs/csf/features/#row-and-column-properties 値の型はRowInfoの配列です。hpx, hptのいずれかのプロパティに数値を渡してあげると変更できます。RowInfoの配列のindexと同じindexの行に適応されます。

const rowInfo = [{ hpx: 60 }, { hpx: 60 }]
sheet[!row] = rowInfo

列の長さを変更したいとき

行の高さの変更と同じようにsheetオブジェクトの!colというプロパティに値を渡します。 https://docs.sheetjs.com/docs/csf/features/#row-and-column-properties 値の型はColInfoの配列です。wpx, width, wchのいずれかのプロパティに数値を渡してあげると変更できます。ColInfoの配列のindexと同じindexの列に適応されます。

const colInfo = [{ wch: 100 }, { wch: 100 }]
sheet[!col] = colInfo

文字数によってwchを変える関数などを作ると便利ですね。

行、列を非表示にしたいとき

同じようにシートオブジェクトの!col, !rowに値を渡してあげる必要があります。 colInfo, rowInfoのhiddenというプロパティをtrueに指定すると非表示になります。

const rowInfo = [{ hpx: 60, hidden: true }, { hpx: 60 }]
const colInfo = [{ wch: 100, hidden: true }, { wch: 100 }]
sheet[!row] = rowInfo
sheet[!col] = colInfo

セルにリンクを張りたいとき

まず、データを変更する必要があります。string型ではなく、CellObject型の値を渡してあげます。 https://docs.sheetjs.com/docs/csf/cell

そこで、l.TargetというプロパティにURLを渡すとそのセルにリンクが貼られます。 リンクと同時にセルの値を挿入したい場合は、vプロパティに入れます。

const data = [
  ["魚種", "適正水温", "餌", "平均体長"],
  [{ v:"イワシ", l: { Target: "https://hogehoge.fuga" }  }, "14~17度", "プランクトン", "20cm"],
  // 省略
];

Sheet.jsの使用感

指定の値を挿入したExcelを出力するくらいのことは結構簡単にできるなと思います。ただ、スタイル(セルの色やフォント)を変えようとしたり、フィルターをつけてあげようとするなど、高度なことをしようとすると無料版ではできないようです。有料版だとできるようになるらしいですが。なので、無料で上記のようにExcelをかなり改造して出力したい場合は、使い勝手の良いExcelライブラリが存在する言語のAPIを作り、そこ経由でダウンロードできるようにするなど工夫が必要だなと思いました。

最後に

AlphaDriveでは一緒に働く仲間を募集しています!

「ちょっと話を聞いてみようかな?」くらいのノリでも大歓迎ですので、軽い気持ちでぜひお問い合わせください! まずはカジュアル面談でお話しましょう!

https://hrmos.co/pages/uzabase/jobs/AD_Eng001

https://www.wantedly.com/projects/506180

https://www.wantedly.com/projects/552238

Page top