やあ、お疲れ様。フロントエンドの現場で「なぜか値が一致しない」「検索機能でヒットしない」という謎のバグに頭を抱えたことはないかな?
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の改善に時間を使えるようになるはずだ。
次は、この知識を持って、検索機能やバリデーションのロジックを見直してみてくれ。きっと新しい発見があるはずだ。応援しているよ。

コメント