[Swap(), 그 기록] 1. 해시태그

Soye Park·2023년 1월 10일
1
post-thumbnail

이 포스팅은 사이드 프로젝트를 진행하며 생겼던 여러 일들에 대해서 기록을 겸하여 작성한 글입니다. 주기적으로 작성하고자 노력은 하지만 블로그 포스팅 한번 하려고 하면 머릿속에서 나만의 언어로 정리도 하면서 작성하다가 몇시간은 사용하는 지라 주기적이지 않을 가능성이 농후합니다. 아자아자...

해시태그 기능 구현

들어가면서...

사이드 프로젝트를 진행하면서 게시글 등록 페이지를 맡게 되었다. 교환나눔 플랫폼을 만드는 지라 해시태그 기능을 구현하는 것이 필요했는데, 벨로그의 해시태그 처럼 뭔가 좀 멋드러지는 UI로 구현하고 싶었다. 하지만 현실은.. 그렇다. 이쁘게 만드는 거에 큰 재능은 없었다...

해시태그는 어떤 기준으로 작성 기준을 세워야 할까?

현재 진행 중인 사이드 프로젝트는 굿즈 상품을 교환 또는 나눔하는 커머스 플랫폼이다. 해시태그로 유명한 트위터나 인스타그램은 글자 수 제한만 아니면 무한하게 해시태그를 달 수 있지만, 상품을 등록해서 해시 검색 및 인기 키워드 파악을 위한 용도라면 그렇게 많은 해시태그는 오히려 독이 될 것 같았다.

그래서 더도 말고 덜도 말고 딱 3개의 키워드만 허용했다. 하지만 3개의 키워드를 허용해도 글자 수가 무제한이거나 너무 적은 글자만 사용해야한다면 불편함이 있지 않겠는가? 그래서 해시태그를 보통 얼마나 길게 작성할까 고민을 해봤다.

"아이유X뉴진스_콜라보_앨범"

어? 이런 게 지금은 없지만 생긴다면 이렇게만 해도 14자잖아? ? 아무래도 15자는 되어야 할 것 같았다. 그래서 15자로 결정.

기능 구현 시작

이벤트핸들러 유형 정하기

keyUp? keyDown? keyPress?

기준이 정해졌으니 시작은 이벤트가 발생하는 상황을 만들어내는 것이었다.

처음에는 keyUp 이벤트를 생각하고 onKeyUp을 사용했으나 치명적인 문제가 발생했다. onKeyUp을 사용하면 키가 올라오면서 두번의 입력이 들어갔다. 그래서 keyDown을 사용하니 동일한 문제는 발생하지 않고 정상적으로 입력됐다.

사실 keyUp과 keyDown 이벤트는 한글과 같은 조합문자에서 모두 비슷한 유형의 발생한다고 하는데, 이상하게 이번의 경우에는 keyUp만 해당 문제를 겪었다.

그리고 서칭을 하던 중 엔터키를 사용하는 것에는 keyPress 이벤트를 사용하는 것으로 해결이 가능하다는 내용을 많이 찾을 수 있었는데, 해당 스펙은 이제는 사용이 권장되지 않는 스펙이므로 (사용 상에 지장이 없으니) keyDown 이벤트를 활용했다.


🚨🚨문제 발생🚨🚨 🕐수정일 2023.01.11🕐

아오!!! Recoil을 활용하면서 처음에는 발생하지 않았던 이중입력 버그가 발생했다. 레거시 코드인 keyPress 이벤트를 너무 사용하기 싫었으나 뭐 마땅한 대안이 없기 떄문에 사용해보려고 했다. 하지만 웬걸..? 얘도 동일 증상이 발생하네..? Recoil을 사용하면서 발생한 버그인 듯 한데.. 일단 이 문제는 마이너한 리코일 + 영어도 아닌 한글이라는 점에서 정보가 제로에 수렴했고... 추후 더 찾아보도록 할 것...

아무튼 겸사겸사 해결책을 더 찾아보고 검색을 하다보니 isComposing 이라는 프로퍼티 값의 존재를 알게 되었는데, 이는 해당 이벤트의 문자가 조합문자인 지 아닌 지를 판별하는 프로퍼티로 한글 같은 조합문자의 키보드이벤트 버그를 해결하기 위해 keyPress와 더불어 가장 많이 사용하는 방법 중 하나였다.

그런 이유로 해당 프로퍼티를 통해 false 일 경우 (조합문자가 아닐 경우) keyDown이벤트가 발생하도록 했다. 왜냐면 애초에 문제가 발생했던 이유는 enter를 누르는 순간에 한글이 조합문자인 지 아닌지를 판단할 수 없기 때문에 두번 실행이 되는 것이었다. 그러므로 false true를 반환하던 isComposing값을 false 일 때만 입력이벤트가 실행되도록 하면 해결 완료

const pressEnterKey = event => {
  if (event.key === 'Enter' && event.nativeEvent.isComposing === false) {
    if (
      hashArr.length >= 3 ||
      hashArr.includes('#' + event.target.value.replaceAll(' ', '_')) ||
      hashtag.length > 16
    ) {
      return;
    } else {
      setHashArr([...hashArr, hashtag]);
      event.target.value = '';
    }
  }
};

value값을 캐치!

 const catchHashtag = event => {
    setHashtag('#' + event.target.value.replaceAll(' ', '_'));
  };

너무 당연하게도 onChange 이벤트를 통해 input에 있는 밸류를 가지고 온다. 쉽다. 다만 해시태그는 공백문자가 허용이 안되기 때문에 공백을 모두 언더바(_)로 대체 시킬 수 있도록 replaceAll()을 한번 사용해줬다.


엔터키를 누르면 데이터가 배열에 들어갈 수 있도록 하자! 근데 몇가지 조건을 곁들여서-

keyCode? key?

이벤트의 유형을 정했으니 어떤 상황에서 발생하는 지를 정해야했다. 해시태그는 뭐 다양한 경우의 수에서 발생하지 않는다. 오직 엔터만을 바라볼 뿐...-

그런데 여기서도 자그마한 문제가 있었다. 처음에는 keyCode === 13 을 활용한 조건을 주었는데, 검색해보는 와중 (사실 이 블로깅을 하는 도중... 구궁) keyCode 역시 구시대의 산물이라는 것을 발견했다... 현재는 event.key === "Enter" 와 같은 key를 활용하는 것이 권장된다는 것이었다. 그래서 바로 수정 후 커밋했다. 휴-


자 이제 조건을 넣어볼까? 최대 3개까지 15자 이하! 아 그리고 또 중복은 안됨!

이 글을 적는 와중에도 히카맹 선생님의 수업이 떠올라 다급하게 리팩토링을 하나했다.

const pressEnterKey = event => {
    if (event.key === 'Enter') {
      if (
        hashArr.length >= 3 ||
        hashArr.includes('#' + event.target.value.replaceAll(' ', '_')) ||
        hashtag.length > 16
      ) {
        return;
      } else {
        setHashArr([...hashArr, hashtag]);
        event.target.value = '';
      }
    }
  };

이벤트 조건문 내에 있는 조건을 if문만 사용하여 모두 옵셔널하게 작성했었으나 조건과 조건이 충족되면 실행될 구문으로 명확하게 나누어 if else문으로 작성하였다.

아무튼 3가지 조건이었던 최대 작성 가능 갯수/중복 불가 조항/15글자 초과에 대해서는 얼리 리턴을 통해 하나만 걸려도 해시태그가 작성되지 않도록 하였고 3가지의 조건 검사가 끝난 후에는 state에 (현재) 작성된 hashtag가 추가된 arr가 들어갈 수 있도록 하였다.


뜬금없는 고난...? key

자 이렇게 배열에 hashtag들을 집어넣었으니 map 메소드를 통해 간단하게 JSX를 return 할 수 있다. 아니 할 수 있었다. 그런데... react의 list key값....!!! 네이놈..ㅠ

문제가 한가지 발생했다.

react는 가상돔과 엘리먼트를 열심히 순회하며 뭐가 바뀐 게 있나 계속해서 비교감시한다. 아주 매섭게..

근데 그걸 하기 위해서는 리액트가 한가지 요구를 해댄다. "key값 작성해, 근데 그것도 중복안됨 ㅎ 유-니-크하게"

그래서 난 생각했다. uuid 전에 (바닐라 자스 에 서 바 닐라 .. ) 써보니까 괜찮던데 유니크한 값을 생산해내는 거니까 딱이지 않나? ㅋㅋㅋ ㄱ! 했다.

하지만 문제가 발생했다.

나는 uuid를 변수에 계속 할당해서 사용하고 싶었다. but.. 할당해서 사용하면 계속해서 이유를 모를 동일한 키가 할당된 게 있다며 타이핑을 할 때마다 엘리먼트가 무한 생성되는 버그가 발생했다. 콘솔을 찍어봤을 때 당장 same-key는 보이지 않았으나 크리티컬한 버그가 발생하니 고치는 수 밖에 없었다. 아직도 이유를 모르겠다. 검색을 해도 같은 오류이나 동일하진 않았다.. 다시 한번 찾아보고 알아내면 수정하겠다.

그리고 해답은 너무 가까이 있었는데, key값에 그냥 uuidv4()를 직접 실행해 주면 되는 것이었다... (...?)

<StyledHashtagWrap key={uuidv4()}>

오 나이스..


는 무슨 말하자 마자 블로그들을 서칭하며 본 자료 중에 uuid는 리액트에서 사용이 부적합하다는 글을 보게 되었다. 사실 uuid를 사용하는 그 자체로 버그가 파바바박 뜨고 하는 건 아니다. 하지만 사용을 지양해야하는 가장 큰 이유가 있었다.

앞서 말했듯 리액트는 가상돔과 렌더링된 엘리먼트들을 매의 눈으로 감시하고 있고 그 감시의 역할에는 key가 꽤 지대한 역할을 하는 중이었다. 그렇기 때문에 유니크한 값으로 각 요소의 고유성을 확고히 하는 것이었는데, uuid 이녀석은 유니크해도 너무 유니크해서 매번 바뀐다. 새로 실행될 때마다 계속 바뀌니 같은 내용의 무언가라도 삭제하고 다시 작성하면 모든 것이 새로와져있다. 이게 문제였다.

새로운 녀석이 온 것처럼 보이니 리액트는 옳다구나하며 새롭게 렌더링을 하게된다. 즉 불필요한 렌더링이 발생한다는 말인데, 아무리 컴퓨터 자원이 넘쳐나는 시대라도 불필요한 자원을 낭비하게 할 수는 없지 않은가? 그러므로 지양을 해야한다.

그래서 내 대안은... 키값은 유니크하고 변하지도 않고 예상도 가능한 값이 베스트 다. 그런 거 바로 눈 앞에 있었다. 해시태그 그 자체.... 해시태그는 어차피 중복도 안되고 그렇기 때문에 유니크하고 딱히 변할 일도 없다.(삭제 빼곤) 베스트네 베스트여.. 그래서 key 값으론 해시태그 그 자체를 선물해줬다. ㅇㅇ...


이제 지우는 기능만 남았쥬?

해시태그 자체가 투두리스트와 유사성이 깊은 느낌의 코드라 그런 지 그냥 쉽고 간편하게 filter 메소드를 활용해서 삭제 기능을 만들었다.
다만 delete btn 안에 있는 리액트 아이콘(Close 모양을 위한)에 click 이벤트를 준 터라 부모의 부모 요소에 접근하기 위해 parentElement.parentElement라는 코드가 눈에 띄어버린다...

const deleteHashtag = event => {
  const arr = hashArr.filter(
    element => element !== event.target.parentElement.parentElement.innerText,
  );
  setHashArr(arr);
};

CSS는?

CSS는 styled-components 를 활용하는 것을 즐기므로 (그리고 사이드에서도 styled-components를 사용함) styled-components로 작성되었으며 기니까 여기에 코드 올리는 건 생략-

비루한 결과물

마치며

블로그 작성은 너무 많은 힘이 든다. 하지만 이렇게 내용들을 정리해두면 언젠가 찾아보게 되니 짧더라도 올려야겠다. 개발조와

참고

리액트에서 uuid를 사용하면 안되는 이유
https://velog.io/@letthere/developic-react-list-key-error-uuid
Key Event
https://velog.io/@corinthionia/JS-keydown에서-한글-입력-시-마지막-음절이-중복-입력되는-경우-함수가-두-번-실행되는-경우

profile
응애FE개발자/ 블로그 이전 : https://soyeah-log.vercel.app/

0개의 댓글