ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JS 글자 수 세기 문제
    Study/개발 2025. 2. 18. 23:16

    글자 수 문제

    JS에서 글자 수를 validation을 하는 경우 보통 length를 사용한다.

    const MAX_LENGTH = 10;
    const validate = (value: string) => value.length <= MAX_LENGTH;
    
    // ex)
    validate('1234567890') // true

    하지만, 아래 경우는 어떨까?

    validate('정답을 맞춰보세요😀');

    띄어쓰기 포함 10글자이지만, false를 출력하는것을 볼 수 있다.

    '정답을 맞춰보세요😀'.length; // 11

    emoji가 길이 2로 계산되기 때문이다.

    해결책

    [...'정답을 맞춰보세요😀'].length; // 10

    위와 같이 spread 연산자를 사용하면 결과가 10으로 정상적으로 나오는 것을 알 수 있다.

    원인

    문자열은 인덱싱, 길이연산 등에서 기본적으로 UTF-16 코드 유닛 기반으로 동작한다.

    '정답을 맞춰보세요😀'[9]; // '\uD83D' (코드 유닛)

    반면 문자열의 이터레이션은 Unicode 코드 포인트 기반으로 동작한다.

    Mdn
    Strings are iterated by Unicode code points. This means grapheme clusters will be split, but surrogate pairs will be preserved.

    즉, spread 연산자나, for ... of를 통해서 반복문을 사용하면, 이모지에 대해서도 정상적으로 사용 가능하다.

    iterator로 순회하는 경우

    👨🏻‍🍳

    하지만 이렇게 간단하게 끝나지 않는다!
    이모지는 더 다양한 형태로 존재하기 때문이다.

    이모지 출처: https://emojipedia.org/man-cook-light-skin-tone

    위 이모지는 길이가 몇일까?

    '👨🏻‍🍳'.length; // 7

    사실, 이 이모지는 여러 이모지의 조합이다.

    '👨🏻‍🍳'.substring(0, 2); // 👨
    '👨🏻‍🍳'.substring(0, 5); // 👨🏻‍
    '👨🏻‍🍳'.substring(5, 7); // 🍳

    밝은 피부톤의 남자 + 후라이와 후라이팬의 조합인데,
    밝은 피부톤의 남자도 남자 + 밝은 톤의 조합이기 때문이다.

    실제로, spread 연산자로 길이 계산해보면, 다음과 같이 분해되서 (깨진채로) 나타난다.

    [...'👨🏻‍🍳'] // ['👨', '🏻', '‍', '🍳']

    이런 애들은 console에서 입력 후 backspace로 지울 때도, 한번에 지워지지 않는다.
    (남자로 변했다가 지워짐)

    한번에 지워지지 않는 이모지

     

    원인

    실제 유니코드 문자와, 사용자가 인식하는 문자의 단위는 다르다.
    사용자가 인식하는 문자의 단위를 Grapheme cluster라고 부른다.
    이는 여러 코드 포인트의 조합이 될 수 있다.

    해결책

    Intl.Segmenter

     

    Intl.Segmenter - JavaScript | MDN

    The Intl.Segmenter object enables locale-sensitive text segmentation, enabling you to get meaningful items (graphemes, words or sentences) from a string.

    developer.mozilla.org

    위 스펙을 사용해서 이런 문제도 해결 가능하다.

    const segmenter = new Intl.Segmenter();
    const segment = segmenter.segment('👨🏻‍🍳👨🏻‍🍳👨🏻‍🍳👨🏻‍🍳의 길이는?');
    console.log([...segment].length); // 10

    또는 https://github.com/orling/grapheme-splitter 같은 라이브러리를 사용할 수 있다.

    정리

    UTF-16 코드 유닛 - 16비트 단위로, 문자를 나타내는 Atom 역할

    Unicode Codepoint - UTF-16 인코딩에서는 코드 유닛 1~2개로 나타냄. 약 221

    Unicode Grapheme cluster - 하나 이상의 코드 포인트 조합으로, 시각적으로 하나로 인식되는 문자를 나타냄.

    결론

    상황마다 다르겠지만 이모지 등 Grapheme cluster 문자를 많이 쓰는 서비스에서는 글자 수를 검증하기 쉽지 않다.
    특히 길이 제한이 짧은 경우 사용자의 기대와 다른 경험을 줄 가능성이 크다.
    반대로 DB에는 일반 텍스트 기준으로 길이 제한이 걸려있는데 FE에서만 Grapheme cluster 기준으로 검증한다면 저장된 데이터가 잘리는 문제가 발생할 수도 있다.

    사용자 인식을 기준으로 글자 수 검증이 중요한 서비스라면 Grapheme Cluster 단위로 계산하는 방식을 고려해보자.
    길이의 제한은 필요하지만 꼭 글자수일 필요가 없는 경우에는 사용자에게 보여주는 제한을 byte로 보여주는 방법도 있다.

    728x90
Designed by Tistory.