Today's/DevelopStory

[ReactNative] 숫자 3자리마다 콤마(",") 삽입하기

Axis 2023. 3. 24. 16:51

네이티브 개발을 진행하다가 오랜만에 ReactNative로 프로젝트를 하게되었다.
일반적으로 숫자 3자리마다 콤마를 삽입하는 가장 편한 방법은 toLocaleString() 메소드를 사용하는 방법이다.
하지만 이는 iOS환경에서는 문제없이 동작하지만, Android 환경에서는 정상적으로 콤마가 출력되지 않는 문제가 있었다.

분명 예전에 ReactNative 프로젝트를 진행할 때 겪었던 문제였음에도 불구하고,
제대로 기록해두지 않으면 이렇게 또다시 반복적인 삽질을 하게 된다...
그래서 이번엔 아예 커스텀 메소드를 구현하면서 이 문제를 정확히 기록해두고자 한다.

Android 환경에서 toLocaleString() 메소드가 제대로 동작하지 않는 이유에 대해서 아주 간략히 설명하자면,
ReactNative는 여러 통합된 자바스크립트 엔진을 여러 플랫폼에 공통적으로 사용한다.
하지만 이 자바스크립트 엔진이라는 녀석은 특정 플랫폼에서 사용되는 브라우저의 엔진일 가능성이 높으며,
다른 플랫폼에서의 호환 문제가 언제든 발생할 수 있다.
따라서 특정 메소드는 Android나 iOS 두 플랫폼 사이에서 차이가 발생할 수 있는 것이다.

만약 이런 상황이 발생한다면 보통은 라이브러리를 사용해서 해결하는게 가장 보편적이겠지만,
콤마 삽입과 같은 간단한 문제는 정규 표현식을 활용해서 커스텀 메소드를 구현하는게 가장 가볍고 최적화된 방식이라 생각했다.

아래는 TypeScript로 작성한 코드 예제이다.

먼저, 정수뿐만 아니라 소수에도 대응하는 함수를 구현하고자 하였다.
따라서 숫자와 소숫점 자릿수를 인자로 받기도 했다.
프로젝트 전반에 걸쳐 자주 사용될 함수이므로 export 해주고,
소수점 자릿수 인자는 기본값을 2로 정해봤다.
반환되는 데이터는 당연히 String이 되겠다.

export const addCommaFormatter = (
  number: number,
  decimalDigits: number = 2
): string => {
  ...
};

다음은 정수와 소수를 할당하는 변수를 선언하고, split() 메소드를 사용하여 소숫점(".")을 기준으로 나누어 할당한다.
이때, 인자로 받은 decimalDigits, 즉 소숫점 자리수를 toFixed() 메소드를 사용하여 정리한다.

...
const [integerPart, decimalPart = ""] = String(
  +number.toFixed(decimalDigits)
).split(".");
...

그리고 정수 파트는 정규표현식을 사용하여 3자리수마다 콤마를 삽입한다.

...
const formattedIntegerPart = integerPart.replace(
  /\B(?=(\d{3})+(?!\d))/g,
  ","
);
...

여기까지 데이터를 모두 가공하였다면 마지막으로 반환 하기 전에
소수점 이하 값과, 인자로 받은 소숫점 자릿수가 모두 0이 아닌지 3항 연산자를 통해 확인한다.

...
return decimalPart && decimalDigits
  ? `${formattedIntegerPart}.${decimalPart}`
  : formattedIntegerPart;
...

다음은 완성된 코드 예제이다.

export const addCommaFormatter = (
  number: number,
  decimalDigits: number = 2
): string => {
  const [integerPart, decimalPart = ""] = String(
    +number.toFixed(decimalDigits)
  ).split(".");
  const formattedIntegerPart = integerPart.replace(
    /\B(?=(\d{3})+(?!\d))/g,
    ","
  );
  return decimalPart && decimalDigits
    ? `${formattedIntegerPart}.${decimalPart}`
    : formattedIntegerPart;
};