[JS] Array.from() 의 재발견

heyday.xz·2024년 10월 20일
11

모청 프로젝트

목록 보기
5/5

제가 개발한 청첩장에는 이모지 컨페티 기능이 있어요.

이 이모지들은 고객님들의 구글 폼에 string 으로 받고 있어요.
공백도 들어올 수 있었어요.

주문이 별로 없었을 때는 요걸 하나하나 쪼개서 수동으로 JSON 형태로 만들었는데요.
["😀","🤓","🥰","😛","😎"]

하다보니 이걸 수동으로 하고 있지 하는 생각이 드는 거에요.
그래서 이모지 분리를 하는 페이지를 만들기로 했어요.

공백 제거하고, split하면 되겠죠?

const stringToEmojiList = (input: string): string[] => {
        const noSpaceInput = input.replace(/\s/g, '');
        return noSpaceInput.split('').filter(char => char.trim() !== '');
};

replace로 중간에 들어올 수 있는 공백을 미리 ''로 변환하고, split해서 배열로 만들어봅니다.
그리고 console.log(JSON.stringify(emojiList, null, 2))으로 출력해봤더니..

어라라 제가 원한건 이게 아닌데용..
유니코드 포인트가 개별적으로 쪼개지는 문제가 발생합니다.

gpt에게 도움을 요청했습니다.
Array.from()이라는 답을 줍니다.

이모지는 일반 문자가 아닌 유니코드로 표현되는 경우가 많기 때문에 단일 문자를 처리할 때처럼 할 수 없고, 이를 해결하기 위해 유니코드 서로게이트 페어를 올바르게 처리해야 합니다.

아래처럼 Array.from()을 사용하면 이모지 하나하나를 올바르게 분리하여 배열로 처리할 수 있습니다.

const stringToEmojiList = (input: string): string[] => {
        const noSpaceInput = input.replace(/\s/g, '');
        return Array.from(noSpaceInput).filter(char => char.trim() !== '');
};

결과는?

오 잘 쪼개지네요.

Array.from의 어떤 점 때문에 이런게 가능할까요?

Array.from()이 유니코드를 올바르게 처리할 수 있는 이유는, 이 함수가 유니코드 써로게이트 페어(surrogate pairs) 를 자동으로 인식하고 하나의 완전한 문자(이모지 등)를 하나의 요소로 처리하기 때문입니다.

유니코드 써로게이트

페어란?
일반적인 문자는 하나의 코드 유닛(UTF-16 코드)으로 표현되지만, 이모지나 일부 복잡한 문자는 두 개의 코드 유닛을 합쳐 하나의 문자로 표현됩니다. 예를 들어, 😀 이모지는 실제로 두 개의 코드 유닛으로 구성됩니다:

  • \uD83D (고위 써로게이트)
  • \uDE00 (저위 써로게이트)
    일반적인 split() 함수나 charAt() 같은 문자열 처리 함수는 문자열을 코드 유닛 단위로 나누기 때문에, 이모지가 하나의 문자로 처리되지 않고 두 개의 코드 유닛으로 나뉘는 문제가 발생합니다.

Array.from()의 역할

Array.from()은 단순히 문자열을 코드 유닛 단위가 아닌 문자 단위로 처리합니다. 즉, UTF-16 써로게이트 페어를 자동으로 인식하여 하나의 써로게이트 페어를 하나의 문자로 처리할 수 있게 해줍니다. 결과적으로 Array.from()을 사용하면 이모지를 하나의 문자로 인식하고, 이모지와 같은 복잡한 유니코드 문자를 제대로 분리할 수 있습니다.

예시

const input = '😀😀😀';
console.log(input.split('')); // ["\uD83D", "\uDE00", "\uD83D", "\uDE00", "\uD83D", "\uDE00"]
console.log(Array.from(input)); // ["😀", "😀", "😀"]

그럼 실제로 고객님들이 많이 사용하시는 이모지를 넣어볼까요. 👰🏻‍♀️🤵🏻


오....... 이건 또 뭘까요
다시 gpt에게 물어봅니다.

👰🏻‍♀️🤵🏻 같은 이모지는 단일 이모지가 아니라 여러 유니코드 코드 포인트가 결합된 이모지입니다. 예를 들어 👰🏻‍♀️는 다음과 같은 구성 요소로 이루어져 있습니다:

  • 기본 이모지 👰 (Bride)
  • 스킨 톤 수정자 🏻 (Light Skin Tone)
  • 성별 기호 ♀️ (Female Sign)

이처럼 여러 코드 포인트가 결합된 이모지를 하나의 유니코드 문자열로 표현하는데, 이를 ZWP (Zero Width Joiner, 0폭 조인자) 를 통해 처리합니다. ZWP는 여러 개의 개별 이모지를 하나로 결합할 때 사용됩니다.

기존의 Array.from()로는 이 경우에도 이모지를 올바르게 분리할 수 없습니다. 이러한 경우에는 유니코드에서 제공하는 grapheme clusters(문자 클러스터) 를 처리할 수 있는 라이브러리를 사용해야 합니다.

해결 방법: grapheme-splitter 라이브러리 사용
grapheme-splitter는 이모지를 포함한 모든 문자를 하나의 그래프 클러스터로 처리하는 유용한 라이브러리입니다. 이 라이브러리를 사용하면 👰🏻‍♀️ 같은 복잡한 이모지도 하나의 단위로 처리할 수 있습니다.


여튼 돌아돌아 다른 방법을 찾아 결국 순수하게 자바스크립트로 문제를 해결하진 못했지만,
Array.from에 대해 다시 찾아보게 되었어요.

Array.from()

Array.from()은 유사 배열 객체나 이터러블 객체를 배열로 변환하는 메서드입니다. 주로 배열처럼 동작하지만 실제 배열은 아닌 객체를 배열로 변환할 때 사용됩니다. 간단히 말해, 이 메서드는 배열이 아닌 것을 배열로 바꿔주는 도구입니다.

주요 사용 예시

1. 유사 배열 객체를 배열로 변환

유사 배열 객체는 배열처럼 보이지만, 실제 배열은 아닌 객체입니다. 예를 들어, argumentsNodeList 등이 이에 해당합니다. Array.from()을 사용해 이를 배열로 변환할 수 있습니다.

function example() {
  console.log(arguments); // 유사 배열 객체
  const argsArray = Array.from(arguments); // 배열로 변환
  console.log(argsArray); // 배열
}

example(1, 2, 3); // 출력: [1, 2, 3]

2. 이터러블 객체를 배열로 변환

이터러블 객체는 반복 가능한 객체로, Set, Map 등이 이에 해당합니다. Array.from()을 사용해 이들을 배열로 변환할 수 있습니다.

const set = new Set([1, 2, 3]);
const arrayFromSet = Array.from(set);
console.log(arrayFromSet); // 출력: [1, 2, 3]

3. 문자열을 배열로 변환

문자열도 이터러블 객체이므로 Array.from()을 통해 각 문자로 구성된 배열을 만들 수 있습니다.

const str = "hello";
const arrayFromString = Array.from(str);
console.log(arrayFromString); // 출력: ['h', 'e', 'l', 'l', 'o']

4. 배열의 각 요소를 매핑하면서 변환

Array.from()의 두 번째 인자로 함수를 전달하면, 배열을 생성하면서 각 요소를 변환할 수 있습니다. map()과 비슷하게 동작합니다.

const array = Array.from([1, 2, 3], x => x * 2);
console.log(array); // 출력: [2, 4, 6]

주로 쓰는 상황

  • DOM에서 여러 요소를 선택할 때 NodeList는 배열이 아닌 유사 배열 객체입니다. 이때 배열 메서드를 사용하려면 Array.from()으로 배열로 변환해야 합니다.
const buttons = document.querySelectorAll('button');
const buttonsArray = Array.from(buttons);
buttonsArray.forEach(button => console.log(button.textContent));
  • Set이나 Map 같은 이터러블 객체를 순서가 있는 배열로 변환하고자 할 때도 많이 사용됩니다.

Array.from()은 배열을 다루는 상황에서 유용한 도구이며, 특히 유사 배열 객체를 다룰 때 매우 자주 쓰입니다.


string을 쪼개서 배열로 만드는 걸 보고 코딩테스트 때 split()을 쓰던 제가 떠오르더라구요.
그러면 split()Array.from() 중에 뭐를 사용하면 좋을까요?

Array.from(input) vs input.split('')

Array.from(input)

문자열을 이터러블 객체로 간주하고, 각 문자를 배열로 변환합니다.
유니코드 문자나 이모지를 처리할 때 Array.from()이 더 안전합니다. 왜냐하면 이모지는 하나의 문자로 보이지만, 두 개의 코드 유닛으로 표현되기도 하기 때문입니다.

const input = "hello";
const array = Array.from(input); 
console.log(array); // 출력: ['h', 'e', 'l', 'l', 'o']

input.split('')

문자열을 빈 문자열 ''을 기준으로 쪼개서 배열로 변환하는 방법입니다.
일반적인 ASCII 문자(영어 알파벳 등)를 처리할 때는 split('')이 더 직관적이고 빠를 수 있습니다.

const input = "hello";
const array = input.split(''); 
console.log(array); // 출력: ['h', 'e', 'l', 'l', 'o']

성능과 상황에 따른 선택

일반적인 상황: 문자열이 단순하고 유니코드 복합 문자를 포함하지 않는다면, split('')이 더 짧고 간결하기 때문에 자연스럽게 사용할 수 있습니다.
복잡한 문자열(이모지 포함): 만약 문자열에 이모지나 유니코드 문자가 포함될 가능성이 있다면, Array.from() 을 사용하는 것이 더 안전합니다.

const emojiString = "😊"; 
console.log(emojiString.split('')); // 출력: ['�','�'] (깨짐)
console.log(Array.from(emojiString)); // 출력: ['😊'] (정상 처리)

이렇게 프로젝트를 하다가 Array.from()을 재발견하는 시간을 가졌습니다.
설마 저만 모르는 거 아니겟쬬..?

0개의 댓글