반년 전에 만들어뒀던 토이프로젝트의 코드를 오랜만에 다시 보니
리액트를 배운지 얼마 안됐을 때 만든 코드다보니 이것 저것 마음에 안드는게 많아
리팩토링을 시행했다.
이것 저것 하다보니 가장 불편한건 못생긴 디자인이였고, 이쁜 디자인은 못할 거 같아서 그냥 깔끔하게만 수정해줬다.
저 사이트는 코드 블록을 이쁘장한 이미지로 변환해주는 사이트다.
HighlightCode 많관부 바랍니다.고화질 이미지는 다운로드 해서 사용하면 좋아용
2025.02.09 업데이트 이후부턴 클립보드에 복사하여 사용하여도 고화질 이미지를 사용 할 수 있습니다. 🎆
이렇게 최대한 정보를 덜어내다보니 너무 과도하게 정보들이 생략 된 거 같아
툴팁을 이용해서 정보를 표현하도록 기능을 추가했다.
툴팁을 구현하는 방법은 여러 방법이 존재하는데 그 방법들과 왜 그걸 선택 안했는지 이야기 해보겠다.
우선 방법 하나는 툴팁 내용을 컴포넌트와 함께 두고 마우스 호버 이벤트에 따라 툴팁을 마운트, 언마운트 하거나 스타일을 변경시켜 툴팁이 나타나게 하는 방법이 있다.
나는 개인적으로 이 방법을 선호하지 않는다.
그 이유는 예를 들어 LanguageSelector
라는 컴포넌트에서 포맷팅 할 코드의 언어를 선택하는 비즈니스 로직과 툴팁을 관리하는 로직이 함께 존재하게 되고
실제 렌더링 되는 컴포넌트와 조건부적으로 렌더링 될 어떤 컴포넌트가 하나의 컴포넌트에 함께 존재하는 것이 관심사 분리 원칙을 지키지 못하기 때문이라 생각하기 때문이다.
그래서 예전 프로젝트에선 조건부적으로 렌더링 할 어떤 요소의 로직을 커스텀 훅으로 만들어둔 채로 반환되는 jsx
와 분리하는 방법을 선택했었다.
이번 프로젝트에서도 그렇게 할까 ? 했다가 다음과 같은 이유로 사용하지 않았다.
사실 1번 이유가 제일 컸지만 굳이 의미를 더 붙혀 생각해보자면
컴포넌트 내부에 비즈니스 로직이 존재할 수록 재사용성이 떨어지고 수정하기가 어렵더라, 예를 들어 위에서 어떤 모달을 여는 컴포넌트가 있을 때 그 컴포넌트를 다른 곳에선 모달을 열지 않도록 만들고 싶다면
useModal
이 내부에 선언되지 않은 형태로 또 만들어줘야 했다.
나는 이게 비효율적이라 생각했고 비즈니스 로직을 외부에서 주입하는 형태로 만들어보면 어떨까 ? 라고 생각했다.
하지만 만들고나서 보니 좀 잘못된 생각이였던 거 같다. 그 이유는 추후 후술한다.
내가 원하는 컴포넌트의 형태는 이랬다.
-> 사용처에서 A와 B를 원할 떄 마다 짬뽕해서 사용
이런 패턴에 HOC 가 가장 적합하다고 생각하여 HOC 를 이용해 툴팁을 구현해봤다.
HOC (Highder order component, 고차 컴포넌트) 란 컴포넌트를 인수로 받아 그 컴포넌트에 특정 로직을 가미하여 새로운 컴포넌트를 반환하는 컴포넌트를 의미한다.
내가 원하는 방법이랑 딱 맞아떨어진다.
툴팁 기능이 없는 컴포넌트 A 를 감싸서 툴팁 기능이 있는 컴포넌트 B로 진화 시키는 방법이니까 말이다.
withTooltip
메소드 전문은 다음과 같이 생겼다.
39-41 번째 줄은 디버깅을 위한 코드로 굳이 필요 없긴 하다.
컴포넌트를 인수로 받아 , 툴팁 텍스트와 툴팁 방향을 주고 다시 호출하여 특정 컴포넌트를 생성하는 메소드이다.
실제 사용처에서는 다음과 같이 사용하며 결과는 다음과 같다.
withTooltip
을 이용해 인수로 받은 컴포넌트를 툴팁과 함께 존재하는 컴포넌트로 진화시키는 메소드를 만들고
그 메소드를 툴팁 텍스트와 방향을 함께 인수로 줘 호출하면 툴팁이 존재하는 컴포넌트를 반환하게 된다.
툴팁 텍스트와 방향 인수를 컴포넌트와 함께 주게 된다면 위처럼 두 번 호출 하지 않고 다음처럼도 쓸 수 있다.
withTooltip(<컴포넌트/> , 툴팁 텍스트 , 방향)
다만 최근 함수형 프로그래밍 뽕에 취해있는 상태라 어깨 너머로 조금 봤던 패턴으로 써봤다. (아직 자세히 공부 안해봐서 잘은 모르지만 ..)
작동은 정상적으로 잘 된다.
저녁에 모든 컴포넌트에 저 툴팁을 적용하고 깃허브에 푸쉬한 후에 개운하게 자려했으나
자기전에 이런 생각이 들었다.
만약 HOC를 안쓰고 저걸 구현하려면 어떻게 해야하나 ? HOC 패턴 짱짱인거 같은데 왜 사장된 패턴이지 ? 저 패턴이 왜 좋지 ? 진짜 저게 좋은 패턴이 맞나?
이런 생각에 잼미니랑 딥식이랑 계속 채팅하며 HOC가 최고라며 나를 설득시켜 달라 하며 얻은 내용은 다음과 같다.
음 그렇군 하고 고개를 끄덕이려해도 잘 안되더라
왜냐면 내 눈에는 HOC 패턴은 이거랑 같아보였기 때문이다.
이러면 위에서 말한 장점 두 가지를 모두 만족하면서 최근에 쓰이는 모던 리액트 패턴과 동일한거 아닌가라는 생각..
때마침 댓글이 61개나 달린 1년전의 게시글이 존재하더라
그래서! 이 글을 쓰며 계속 뒤져보고자 한다.
그러자 답글자는 에러바운더리 같은건 훅으로 대체 할 수 없다고 이야기 한다.
부모 컴포넌트로 감싸는건 괜찮다고 생각하는 거 같다.
훅에서 렌더링에 관여하는 패턴은 올바르지 않다. 렌더링 로직은 컴포넌트에서 이뤄져야 한다.
그래서 HOC 를 쓰는게 괜찮아보인다. 라는 의견이 있다.
실제로 만약 이런패턴으로 쓴다면 ?
구려보이는건 확실하다.
이것 또한 공감하는 바이다.
HOC를 훅과 함께 사용하게 되면 신경 써야 하는 패턴이 기존 패턴 + HOC 까지 추가되게 되니
차라리 그럴빠엔 기존 패턴인 부모 컴포넌트로 감싸버리면 되니 말이다.
악마래
무려 7년전 , 클래스형 컴포넌트를 이용 할 때의 이야기임에도 불구하고 HOC를 사용하지 않는 편이 좋은 이유에 대해 명확히 설명해준다 생각한다.
이런 내용을 이야기 한다.
생각해보면 너무나도 맞는 말인 거 같다.
우선 위에서 이야기했던 이 부분
재사용성 때문에 HOC 를 사용했던 부분을 생각해보자
툴팁을 나타내는 어떤 컴포넌트가 다른 곳에서 툴팁 없이 재사용 될 일이 얼마나 있을까?
좀 더 일반적으로 이야기해보면 , 로그인 한 사람에게만 보여야 하는 컴포넌트가 있어 그 컴포넌트를
withAuth(Component)
형태로 만들어줬을 때 저 컴포넌트가 withAuth
없이 다른 곳에서 사용 될 경우가 있을까? 대부분 없을 것이다.
그러니 굳이 비즈니스 로직을 외부로 뺄 필요가 없다. 차라리 안에서 정의하든 , hook을 컴포넌트안으로 집어 넣든 하여 의존성을 만들어버리는것이 나을 것이다.
3번 의견인 HOC를 사용하면 HOC를 통해 주입된 로직을 외부에서 알기 힘들다라는 것도 생각해보면 맞다.
withTooltip
으로 툴팁이 적용된 여러 컴포넌트들을 사용처에서 살펴보자
외부에서 바라봤을 떄 저 안에서 툴팁 로직이 존재하는지, 존재하지 않는지 알기 힘들다.
뭐 컴포넌트 이름을 어쩌구 저쩌구WithTooltip 이런식으로 작성하는 방법도 있긴 하겠다만
툴팁 로직과 컴포넌트의 본연 로직 (예를 들어 포맷팅 할 언어를 선택하는 로직) 을 분리하고 싶었던 내 의도와는 다르기 때문이다.
차라리 이렇게 쓰면 어떨까
차라리 나아보인다.
render props 패턴도 존재하지만 그건 너무 귀찮다. props 타입 설정 하기도 귀찮고 props로 받는 렌더링 할 어떤 컴포넌트를 내부 이벤트 핸들러에 부착하는 것도 귀찮고 ..
차라리 이런 방식으로 나중에 다시 만들어봐야겠다.
HOC를 어제 처음 써보고 이렇게 생각해봤는데 내가 틀렸을 수도 있고 더 이야기 하고 싶은 부분이 많을 수 있다 생각한다.
부디 어떤 의견이든 생각나는게 있다며 이야기 해주면 좋겠다. 🥹
HOC 로 새로운 컴포넌트를 직접 만들지 않고 부모 컴포넌트단에서
툴팁과 관련된 로직만을 다루도록 말이다.
이렇게 하게 되면 외부에서 해당 컴포넌트에서 어떤 일이 일어날지 알기 쉬울뿐더러
툴팁 로직을 제거하고 싶거나 수정하고 싶은 경우엔 WithTooltip
컴포넌트만 수정하면 되니
매!우! 괜찮아보인다.
다만 내 사이드프로젝트에선 대부분의 컴포넌트가
WithTooltip
으로 감싸져있어서
전체 코드를 보면 좀 웃기게 생겼다. 🫠