【実務・中級編】 Unicode正規化形式(NFC/NFD/NFKC/NFKD)の概念 – JavaScript実践ガイド

やあ、お疲れ様。フロントエンドの現場で「なぜか値が一致しない」「検索機能でヒットしない」という謎のバグに頭を抱えたことはないかな?

APIから受け取ったユーザー名と、ローカルで保持しているデータ。見た目は全く同じなのに `===` で比較すると `false` が返ってくる。コンソールを眺めて深いため息をつく……。その原因の9割は、Unicodeの「正規化(Normalization)」の問題だ。

今日は、中級エンジニアが避けて通れないこの「目に見えない地雷」について、アーキテクトの視点から紐解いていこう。

—

なぜ「見た目が同じ」なのに一致しないのか?

Unicodeには、同じ文字を表現するのに複数のデータ形式が存在する。例えば「が」という文字。これをコンピュータは以下の2つの方法で保持できる。

1. 合成済み文字: 「が」という一つのコードポイント(U+304C)
2. 分解文字: 「か」(U+304B)+「濁点」(U+3099)の組み合わせ

人間には全く同じ「が」に見えるが、メモリ上のバイト列としては全くの別物だ。これを放置して文字列比較や `includes` を使うと、検索が機能しなかったり、バリデーションで弾かれたりする。フロントエンドでユーザー入力を扱う以上、この揺らぎを吸収するのは我々の義務だ。

Unicode正規化形式の4つの顔

JavaScriptでは、`String.prototype.normalize()` メソッドを使って、この揺らぎを統一できる。まずはこの4つの形式を頭に叩き込んでおこう。

  • NFC (Canonical Composition): 分解されている文字を合成する。これがWeb標準のデフォルトであり、迷ったらこれを使え。
  • NFD (Canonical Decomposition): 合成されている文字を分解する。濁点やアクセント記号を分離したい場合に使う。
  • NFKC (Compatibility Composition): 全角数字や合字を標準的な文字に変換する。例えば「①」を「1」に、「ガ」を「ガ」にする。検索性を高めるなら最強の選択肢だ。
  • NFKD (Compatibility Decomposition): 分解した上で、互換性のある文字を標準化する。

実務で最も頻繁に遭遇するのは「全角半角の揺らぎ」や「濁点の分離」だ。これらを解決するための実践的なコードを紹介しよう。

実務で使える正規化コード

以下のコードは、バックエンドへ送信する前や、検索キーワードのクレンジングを行う際によく使うパターンだ。

/

  • 文字列の揺らぎを統一するためのユーティリティ
  • @param {string} input – 処理対象の文字列
  • @returns {string} – 正規化された文字列

/
function normalizeString(input) {
if (typeof input !== ‘string’) return ”;

// 1. NFKC形式で正規化(全角→半角、合字の分解などを統合)
// 検索エンジンの実装や、ユーザー入力のバリデーションに最適
return input.normalize(‘NFKC’);
}

const str1 = ‘ガ’; // 合成済み
const str2 = ‘ガ’; // 分解済み(カ + 濁点)

console.log(str1 === str2); // false (見た目は同じなのに!)

const normalized1 = normalizeString(str1);
const normalized2 = normalizeString(str2);

console.log(normalized1 === normalized2); // true (これぞ正規化の力)

// 応用:検索機能で大文字小文字や全角半角を無視したい場合
const searchTarget = ‘ABC’;
const keyword = ‘abc’;

// 両方をNFKCで正規化し、小文字に変換して比較する
const isMatch = searchTarget.normalize(‘NFKC’).toLowerCase().includes(keyword.toLowerCase());
console.log(isMatch); // true

現場で意識すべき「泥臭い」ポイント

理論はわかったと思う。でも、現場ではもう一歩踏み込んだ配慮が必要だ。

1. DB保存のタイミング: 基本は「入力値を受け取った直後」に正規化してメモリに乗せる。DBに保存する際も正規化済みか確認しよう。後から修正するのはデータ移行コストが高すぎて地獄を見る。
2. パフォーマンスへの配慮: `normalize()` は重い処理ではないが、数万件のリストを一気にレンダリングするようなシーンでループ内で叩き続けると、当然メインスレッドを占有する。必要な場所でピンポイントに行うのが鉄則だ。
3. 「見た目」と「データ」の分離: ユーザーには入力した通りの文字を見せたい(全角の「1」を入力したいユーザーもいる)。だからといって、内部ロジックまで全角の「1」で持ち回るな。表示用はそのまま、比較・保存用は正規化済み、という二重構造を意識してくれ。

最後に:なぜ知っておくべきなのか

フロントエンドの仕事は、単にDOMを操作することじゃない。ユーザーが自由に入力する「カオスなデータ」を、コンピュータが正しく扱える「整ったデータ」に変換し、橋渡しすることだ。

Unicodeの正規化を知っているだけで、君が書くコードの信頼性は一段階上がる。デバッグで「なぜ動かない?」と悩む時間が減り、より本質的なUI/UXの改善に時間を使えるようになるはずだ。

次は、この知識を持って、検索機能やバリデーションのロジックを見直してみてくれ。きっと新しい発見があるはずだ。応援しているよ。

コメント

タイトルとURLをコピーしました