はじめに
こんにちは!
最近、業務で Node.js を使って Excel ファイルを操作する機会がありました。
ライブラリとして ExcelJS を採用したのですが、スタイルの適用で思わぬ落とし穴にはまったので、自戒を込めて共有します。
「特定のセルだけ色を変えたいのに、なぜか他のセルまで色が変わっちゃう…!」
という現象に遭遇した方の助けになれば幸いです。
やりたかったこと
ユーザーがアップロードした Excel ファイルを読み込み、入力内容にエラーがあるセル(例えば B2)の背景色を「赤」にして、エラーファイルとして返却する処理です。
イメージ
- Excelファイルを読み込む
- セル
B2の値をチェック(エラー発見!) - セル
B2の背景色を赤にする - 保存する
起きたこと
実装して動かしてみると、恐ろしいことが起きました。
私:「よし、B2セルだけ赤くなってるかな?」
Excel:「B2も赤くしたけど、ついでにB3もB4も、なんならC列のセルも赤くしておいたよ!」
私:「えぇ……(困惑)」
指定していないはずのセルまで、スタイルが変更されてしまったのです。
原因:スタイルの「実体」は共有されていた
原因は、ExcelJS(および Excel ファイルそのもの)が、スタイルをメモリ節約のために「共有」していることを知らなかった点にありました。
失敗したコード
初学者だった私は、以下のように書いていました。
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('template.xlsx');
const worksheet = workbook.getWorksheet(1);
const cell = worksheet.getCell('B2');
// 【NG】直接プロパティを書き換えてしまった
// これだと、このスタイルを使っている全セルに影響が出ます
cell.style.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFF0000' } // 赤色
};
await workbook.xlsx.writeFile('result.xlsx');
一見良さそうに見えますが、これが罠でした。
なぜ他のセルまで変わるの?(例え話)
この現象をわかりやすく説明すると、**「回覧板」や「共有のルールブック」**のような状態です。
Excel ファイルの中では、データ量を減らすために、同じ見た目のセルたちは**「1つのスタイル定義(スタイルオブジェクト)」をみんなで参照(共有)**しています。
- B2もB3もB4も、元々は「標準スタイル」という1つのルールブックを見ていました。
- 私は「B2セルだから」と思って、B2が持っているルールブックに赤ペンで「背景は赤!」と書き込みました。
- すると、同じルールブックを見ていた B3 や B4 にとっても、ルールブックの内容が「背景は赤!」に書き換わってしまったのです。
プログラミング用語で言うと、「参照渡し(Reference)」されているオブジェクトを直接変更してしまったことが原因です。
解決策:新しいスタイルオブジェクトを割り当てる
解決策はシンプルです。
「みんなで見ているルールブック」に書き込むのではなく、「そのセル専用の新しいルールブック(オブジェクト)」を作って渡してあげる必要があります。
修正後のコード
const cell = worksheet.getCell('B2');
// 【OK】現在のスタイルをコピーして、新しいオブジェクトとして割り当てる
// 1. 現在のスタイルをコピー (スプレッド構文などを使用)
// これで「共有ルールブック」のコピー(新しい実体)が作られます
const newStyle = { ...cell.style };
// 2. 新しいオブジェクトに対して変更を加える
newStyle.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FFFF0000' }
};
// 3. セルに新しいスタイルオブジェクトをセットする
// これで、このセルは共有のスタイルから切り離されます
cell.style = newStyle;
こうすることで、ExcelJS は「お、このセルには別のスタイルが設定されたな」と判断し、他のセルに影響を与えることなく B2 セルだけを赤くしてくれます。
補足:部分的な書き換えも注意
cell.font だけ変えたい場合も同様です。
cell.font.bold = true とすると、共有されているフォント設定が書き換わる可能性があります。
cell.font = { ...cell.font, bold: true } のように、オブジェクトごと入れ替えるのが安全です。
まとめ
-
事象: ExcelJS でセルの
styleプロパティの中身を直接書き換えたら、他のセルの見た目まで変わってしまった。 - 原因: スタイルオブジェクトは複数のセルで共有(参照)されているため、大元のオブジェクトを書き換えてしまっていた。
-
対策:
cell.style.fill = ...と書くのではなく、cell.style = { ... }のように、新しいオブジェクトを作って代入することで参照を切る。
オブジェクトの参照について理解が深まる良い失敗でした!
同じ現象で悩んでいる方の参考になれば嬉しいです。