이게 무슨 보쌈 싸먹는 소리냐?
그렇지만 이 글은 꽤 흥미롭다. 차근 차근 알아가보자.
나는 선뜻 tailwind가 좋다는 말을 듣고 헐래벌떡 npm install 해버린 사람이다.
어디선가 주섬주섬 주워 들은 tailwind의 장점은 이미 개발자 세계에 퍼져있는 바이럴 전단지에 가깝다.
예컨데
한 마디로 쉽고 빠르게 퀄리티 좋은 제품 만드는 데 얘만한 것이 없다는 것이다.
그리고 직접 사용해보니 이게 맞긴 하다는 생각이 들었다.
그러나 시간이 지나자 tailwind가 만들어 낸 도파민은 사실 먹음직스러운 함정카드라는 사실을 깨닫게 되었다.
const TOC = (props) => <nav className="fixed top-0 left-[5%] hidden xl:flex flex-col justify-center items-start gap-0 h-full w-fit max-h hover:text-black dark:hover:text-gray-200">{props.children}</nav>
이럴수가... 과연 내가 불과 3일전에 적은 nav
className
은 어떤 스타일 용도일까?
나 자신을 책망하고 꾸짖게 되는 순간이다. 말이 안되는 것이다.
정신을 부여잡고 리팩토링 하고자 뇌를 깨워보았다.
(할 뻔)
그래서 시도를 한 것이 여러 방법이 있다.
object
로 비슷한 클래스 명 끼리 나눠주기clsx
패키지로 줄 구분하기일단 첫 번째로 재사용 가능한 class의 최소 단위로 컴포넌트를 쪼개는 것이다.
공식 문서도 이렇게 하라고 지침이 있으나, 복잡한 스타일은 필연적으로 길고 긴 클래스 문자열을 피할 수 없다.
그리고 tailwind className의 복잡성을 줄이기 위해 컴포넌트 단위의 추상화가 가미 되어야 한다는 점도 아쉬웠다.
object
추출두번째 방법이다. 바로 object
로 클래스 구분 용도를 쪼개 주는 것이다.
예컨데 이런 식이다.
const toc = {
position: "fixed top-0 left-[5%]",
layout: "hidden xl:flex flex-col justify-center items-start gap-0",
box: "h-full w-fit max-h",
text: "hover:text-black dark:hover:text-gray-200",
} as const
key로 마치 css className처럼 용도를 적고, value로 해당 클래스를 넣으니 조직력은 좋아졌다. 그러나 사용하며 번거로운 문제점 2가지에 직면했다.
첫째는 매번 obejct
를 정의해야한다는 것이다. 이것은 정말 귀찮은 과정이며, 개발 경험이 구리다.
const $ = (obj: Record<string, string>) => Object.values(obj).join(" ")
const SomeComponent = (props) => <some className={$(classNameObject)}></some>
그렇다 일단 util함수 하나를 만들어주고, 매번 obejct를 용도에 따라 구분한 다음 정의해주는 일은 결과적으로 생산성을 저하시킨다. 이러면 css in js 쓰지...
두번째는 tailwind 공식 자동 완성 플러그인이 동작하지 않는다. tailwind 자동완성 설정의 RegExp를 수정해서 바꾸는 방식이 있기는 하지
그래서 이 방법은 포기했다.
clsx
패키지 사용마지막 방법은 흔히 tailwind를 사용할때 자주 쓰는 clsx
패키지, 일명 classname 생성 함수를 설치해서 보기 좋게 쪼개 주는 방식이다.
import clsx from "clsx"
const Box = ({ children } = (
<div
class={clsx(
"flex items-center justify-center",
"p-4 m-4 bg-white rounded-m",
"md:p-6 md:m-6",
"hover:bg-neutral-50",
"dark:bg-neutral-900 dark:hover:bg-neutral-800"
)}
>
{children}
</div>
))
그러니까 줄바꿈을 자연스럽게 하는 방법인 것이다. 줄을 나눠주고 간단한 조건부 스타일링이 들어갈 때는 clsx
또 줄별로 원하는 속성을 분리할 수 있기에 꽤 괜찮다. 그러나 여전히 property의 집합을 이해하려면 읽어 가야한다는 점, 조건부 스타일링이 복잡해지면 이해하기 힘들어지는 아쉬웠다.
지금까지 tailwind 꿀팁 모음을 읽고 이를 적용해 본 내 경험을 이야기 해보았다.
사실 그냥 이게 tailwind의 재미지 하며 못 본척 사용할까 하면서도 속쓰린 나의 개발 양심을 참지 못하고 넘어선 안될 강을 건너기로 했다.
곧바로 tailwind를 css in js처럼 각성 시키고자 하는 원대한 욕망을 풀기로 결심했다.
즉 내 욕심은 이러한 것들이다.
tailwind는 자체 타입을 지원하지 않는다. 즉 타입스크립트랑 사용할 때 불안하다.
자동완성 플러그인 쓰면 되지, 인생 왜 힘들게 사냐?
이것은 참이나, 결국 한계가 있다는 것을 쓰면서 알게 된다...
해줘요 타입스크립트!
그리고 타입의 부가적인 장점은 바로 props
로 스타일 property
를 전달할 때 빛을 발휘한다.
type TailwindRadius = "roundend" | "...모든 rounded property"
interface borderRadius_좀_주세요_props {
radius: TailwindRadius
children: React.ReactNode
}
const BorderRadius_한개만 = ({ radius, children }: borderRadius_좀_주세요_props) => (
<div className={`${radius}`}>{children}</div>
)
이제 BorderRadius_주세요
의 radius
prop은 타입스크립트의 literal에 의해 자동 완성이 되기 때문에 마음이 편안하다.
tailwind는 특히 조건부 스타일링을 적용하기 까다롭고 가독성 또한 구리다. 앞서 설명했던 모든 방법을 동원해야 하기 때문이다. 그래서 stitches로 비롯된 variants 기반 조건부 스타일링 방식을 가미했으면 좋겠다는 생각을 했다.
또한 tailwind를 쓰면 자주 발생하는 상황이 있다. 바로 공식문서를 한번씩 보는 일이다. 개발 하다가 문서보고, 정말 은근 신경쓰이는 일이다. 한 여름에 잡을 수 없는 광속의 초파리 한놈이 눈 앞에 아른 거리는 상황인 것이다. 만약 이런 과정을 단축시킬 수 있다면 어떨까? jsdoc의 @link를 활용해 공식문서로 직접 연결되는 것이다.
또한 최대한 익숙하고 굉장한 가독성과 개발 경험 챙길 수 있는 형태가 무엇인지 곰곰이 생각해보았다. 그렇다 약 2년전에 코로나처럼 번진 css in js 형태가 그 해답이라는 것을 깨달았다.
예컨데 이런 것이다.
const box = tailwind({
display: 'flex',
alignItems: 'items-center',
justifyContents: 'justify-center',
backgroundColor: 'bg-gray-50',
'@dark': {
backgroundColor: 'dark:bg-gray-900'
}
})
즉 처음에 했던 object
노가다를 자동화 하면서, 자연스레 css in js의 가독성 및 개발경험의 이점을 챙기는 꿩먹고 알먹는 방법인것이다.
그러나 말이 쉽지 코드의 구현은 결코 쉽지 않았다.
그래서 tailwind를 타입스크립트와 연결시키려는 다양한 엽기적? 시도를 찾아보았다.
1. https://typewind.dev
2. https://github.com/muhammadsammy/tailwindcss-classnames
이 둘이 대표적인 형제들인데... 앞서 설명한 원하던 방향과는 다소 디자인적 거리감이 느껴졌다.
사먹고 싶지만 뭔가 아쉬운 음식점인 그냥 내가 음식점을 차려보기로 했다.(이게 무슨 말이지)
오 이건 정말 골치아팠다.
이런 문제들이 정말 많았다. 모든 구간에서 난관에 봉착해서 머리를 싸매는 날이 정말 많았던 것으로 기억한다.
과연 끝도 없어 보이는 미노타입스크립트의 미로에서 나는 어찌 벗어날 것인가? 2부에서 계속...
많은 도움이 되었습니다, 감사합니다.