この記事は、Bill One開発Unit ブログリレー2025の第12弾になります。

こんにちは、技術本部 Bill One Engineering Unit の近藤です。2025年4月にSansan株式会社へ新卒入社し、現在は仕訳・費用按分機能の開発を担当しています。
入社と同時期に立ち上がった仕訳入力の刷新プロジェクトで、AG Gridというグリッドライブラリを導入しました。本記事では、AG Gridを導入した背景と選定基準、そして実装で直面した課題と解決策を紹介します。
目次
背景
Bill Oneにおける仕訳入力は、サービスの根幹を支える機能です。従来の仕訳入力画面は証憑と仕訳の一覧性は高かった一方で、入力体験に課題を抱えていました。
そうした中、仕訳機能を非経理ユーザーにも提供することとなり、専門知識や慣れに依存しない「直感的で、迷わず、ストレスなく扱える」表計算ソフトのような入力体験が必須要件となりました。従来の画面ではこの要求水準を満たせず、抜本的な見直しが必要になりました。
しかし、求められる機能をすべて自社で実装するには膨大なコストがかかります。そこで、外部のグリッドライブラリを導入することにしました。
選定基準
グリッドライブラリの選定にあたり、次の観点で評価しました。
技術要件
- React 18 + TypeScriptで動作し、Bill Oneのフロントエンド技術スタックに適合すること
入力体験
- キーボード主体の編集、容易なセル移動、複数セルのコピー&ペースト、右クリックでの行操作、範囲選択による一括編集など、表計算ソフトの操作感を再現できること
- 数千〜数万行の大規模データでも仮想スクロールにより快適に動作すること
長期運用
- 公式ドキュメントやサンプルコードが豊富で、学習・実装がスムーズに進められること
- 更新頻度が安定して高いこと
- 継続的に開発されており、商用利用の実績があること
- 有事の際に問い合わせできる窓口があること
AG Gridを採用した理由
複数のグリッドライブラリを検討した結果、選定基準を満たし、かつ次の点で優れていた有償版のAG Gridを採用しました。
実装がシンプルでメンテナンスしやすい
イベント発生時の対象セルを容易に特定でき、セルの種類ごとに複雑な条件分岐を書く必要がありません。コードの可読性が高く、長期的なメンテナンスコストを抑えられます。
ドキュメントとコミュニティが充実している
公式ドキュメントが非常に豊富で、Web上でサンプルの表を実際に操作しながら対応するコードを確認できます。コミュニティも活発で、GitHubでissueを上げると数日以内に反応があり、開発チームが優先度を判断し、継続的に修正が取り込まれます。
継続的な開発と有償サポート
月に1回以上のペースでアップデートされており、長期運用においても安心感があります。有償版ではサポート対応も受けられるため、有事の際にも対応しやすい体制が整っています。
Module Registryによるバンドルサイズ最適化
AG GridではModule Registryを通じて、必要な機能だけをモジュール単位で選択して読み込むことができます。多機能なライブラリでありながら、使用しない機能のコードを含めずに済むため、バンドルサイズの極端な肥大化を防ぐことができます。
実装で直面した課題
AG Gridを導入して画面開発に取り掛かりましたが、一筋縄ではいかず苦労した点もありました。ここではその中から1つを紹介します。
範囲操作と状態更新の問題
AG Gridでは、変更されたセル単位で変更イベントが発火します。そのため、範囲操作すると極めて短い間隔で複数の変更イベントが発生します。この影響で、Bill Oneで採用しているFormライブラリ「Formik」の状態更新がほぼ同時に実行されます。各更新が変更前の同じスナップショットを参照するため、最後の変更のみが反映される問題が発生しました。また、値の変更によってデータ補完や整合性を担保するための処理も走るため、変更のたびに重い処理が同時に実行され、パフォーマンスにも影響が出ました。
どのように解決したか
AG Gridには、範囲操作の開始と終了を通知するイベントがあります。onPasteStart/onPasteEnd、onCellSelectionDeleteStart/onCellSelectionDeleteEndは、変更イベントの前後に発火します。これらを利用して、範囲操作の開始時に変更情報を一時的に配列へ格納し、終了イベント発火時にまとめてFormに反映します。これにより、状態更新の回数を最小限に抑えられました。
// 1. 範囲操作中かどうかのフラグと、変更を一時格納する配列 const isRangeOperationRef = useRef(false) const pendingChangesRef = useRef<CellValueChangedEvent[]>([]) // 2. 範囲操作の開始イベント const handleRangeOperationStart = () => { isRangeOperationRef.current = true pendingChangesRef.current = [] } // 3. セル変更イベント(範囲操作中は一時格納、それ以外は即時反映) const handleCellValueChanged = (event: CellValueChangedEvent) => { if (isRangeOperationRef.current) { pendingChangesRef.current.push(event) } else { applyChange(event) } } // 4. 範囲操作の終了イベント(一括反映) const handleRangeOperationEnd = () => { isRangeOperationRef.current = false bulkApplyChanges(pendingChangesRef.current) pendingChangesRef.current = [] } // 5. イベントハンドラの適用 <AgGridReact onPasteStart={handleRangeOperationStart} onPasteEnd={handleRangeOperationEnd} onCellSelectionDeleteStart={handleRangeOperationStart} onCellSelectionDeleteEnd={handleRangeOperationEnd} onCellValueChanged={handleCellValueChanged} // ... />
ユーザーからの反応
苦労した点も多々ありましたが、新UIと非経理ユーザーへの機能公開を無事リリースできました。
リリース後、お客様からは「コピー&ペーストができるようになり、入力体験が向上した」といったAG Gridを導入したことが理由となったポジティブな反応をいただくことができました。一方で、旧画面にあった一覧性の高さが損なわれ、確認がしづらくなったというお声もいただいています。開発チームとして、より良い体験を提供できるよう、日々改善を重ねていきます。
まとめ
本記事では、Bill Oneの仕訳入力画面にAG Gridを導入した背景から、選定基準、実装で直面した課題と解決策までを紹介しました。
- 導入背景
- 非経理ユーザーへの拡大に伴い、表計算ソフトのような入力体験が必須に
- 選定基準
- 技術スタックへの適合、操作感、長期運用の安定性
- 課題と解決
- 範囲操作時のイベント多発を、集約し一括反映することで解決
今回のプロジェクトを通じて感じたのは、Bill Oneの開発組織はユーザー価値を追求するために大胆な意思決定ができる環境だということです。仕訳入力画面を刷新するのは決して小さな挑戦ではありませんでしたが、「ユーザーにとって本当に良い体験とは何か」を軸に議論し、実行に移すことができました。
新卒1年目からこうしたプロジェクトに携われたことは、エンジニアとして非常に貴重な経験でした。ユーザー価値をどこまでも追求する姿勢が組織全体に根付いているBill Oneで、これからも良いプロダクトを作っていきたいと思います。
Sansan技術本部ではカジュアル面談を実施しています

技術本部では中途・新卒採用向けにカジュアル面談を実施しています。Sansan技術本部での働き方、仕事の魅力について、現役エンジニアの視点からお話しします。「実際に働く人の話を直接聞きたい」「どんな人が働いているのかを事前に知っておきたい」とお考えの方は、ぜひエントリーをご検討ください。