【React】JSでの絵文字の文字数カウント、そろそろ決着つけませんか?
概要
JSでフォームに入力されている文字数などをカウントする際、length
では絵文字などのサロゲートペア文字の判定が正確にできない。
絵文字の判定もできないが「𩸽」(ホッケ)などの特殊な漢字もサロゲートペア文字に含まれる。
今回はReact(JS)でこのようなケースでどのように対処するべきか、色々挙げて見ようと思います。
JSの文字列とサロゲートペアについて
JSでは絵文字も含んだ文字をUTF-16
という方式で保存しており16ビットで1文字と判定される。
しかし、二つ以上の文字コードを使用している絵文字などのサロゲートペア文字は1文字で32ビットや64ビットとなっているものがあるため、JSで正確に扱うのが難しい。
lengthでの判定
実際にlength
を使用して文字数を判定してみる。
import { ChangeEvent, useState } from "react";
const CountTest = () => {
const [input, setInput] = useState('');
const onChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value)
}
return (
<div>
文字数: {input.length}<br/>
<input type="text" onChange={onChangeInput} value={input} />
</div>
)
};
export default CountTest;
シンプルにinputの値をstateに入れてlengthで判定してみる
このように絵文字によってもlengthで読まれるカウントは異なる。
string.split('')を使用して配列に変換してからlengthを使う
次に試したのが、文字列を一旦配列にしてから、lengthを使うという方法です。
文字数: {input.split('').length}
これも二つのコードポイントが配列に変換されただけで、正確に扱えませんでした。
ライブラリを使う
そろそろ、詰んできそうなのでライブラリを探すことに。そこで見つけたのがrunes2です
絵文字を完全にサポートする Unicode 対応の JS 文字列分割。
絵文字やその他の非 BMP コード ポイントを変更することなく、文字列をその構成文字に分割します。
うん。なんか使えそうですね。正確に配列に分割してくれそう。
それでは実際に使ってみることに。
yarn add runes2
import { runes } from "runes2";
....
const inputCount = useMemo(() => {
return runes(input).length
}, [input])
return (
<div>
文字数: {inputCount}<br/>
<input type="text" onChange={onChangeInput} value={input} />
</div>
)
行けましたね。一旦配列にする手間は必要ですが、JSでもサロゲートペア文字を扱うことが可能みたいです。
外部ライブラリに頼らない方法としてはIntl.SegmenterというAPIを使って文字列を書記素単位に分解する方法をあるみたいです。
const inputCount = useMemo(() => {
const segmeter = new Intl.Segmenter('ja-JP', { granularity: 'word'});
return Array.from(segmeter.segment(input)).length
}, [input])
が、懸念点もあるみたいなので、個人的には外部ライブラリを使うのがいいかなと思います。
文字列の世界は想像以上に深いと感じました。
参考
Intl.Segmenter で和文の改行をいい感じにしてみる
特殊な文字「サロゲートペア」について
JavaScript: 文字数を正確にカウントするには?
Discussion