🤷‍♂️

【React】JSでの絵文字の文字数カウント、そろそろ決着つけませんか?

2023/02/11に公開

概要

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です
https://github.com/bluelovers/ws-string/tree/master/packages/runes2#readme

絵文字を完全にサポートする 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を使って文字列を書記素単位に分解する方法をあるみたいです。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter

const inputCount = useMemo(() => {
    const segmeter = new Intl.Segmenter('ja-JP', { granularity: 'word'});
    return Array.from(segmeter.segment(input)).length
}, [input])


が、懸念点もあるみたいなので、個人的には外部ライブラリを使うのがいいかなと思います。
https://zenn.dev/notfounds/articles/58c465d4029dc1
文字列の世界は想像以上に深いと感じました。

参考
Intl.Segmenter で和文の改行をいい感じにしてみる
特殊な文字「サロゲートペア」について
JavaScript: 文字数を正確にカウントするには?

Discussion