CSS-in-JS 를 사용하는 이유는 아래와 같다
- CSS 격리
- CSS 를 분리해두면 파일이늘어나고, 이를 불러와 사용해서 상태에 따라 유동적으로 스타일링을 적용하려면 상대적으로 어려움 + 코드가 지저분해짐
- 각 컴포넌트내에 CSS 코드가 존재하기때문에 작업시 가시성이 좋음
- SASS 처럼 중첩 스타일 규칙 작성 가능
- 라이브러리들과 레퍼런스들이 잘 구축이 되어있으며 현재도 발전중(현재는 zero-runtime 을 주장하는 CSS-in-JS 라이브러리가 등장)
https://www.npmtrends.com/@stitches/react-vs-emotion-vs-stitches/react-vs-styled-components-vs-jss
위 4가지 이외에도 라이브러리들이 더 존재한다.
# With npm
npm install @stitches/react
# With yarn
yarn add @stitches/react
이번에 사내에서 외주 react 프로젝트를 진행하게 되어서 이전에는 CSS-in-JS 관련한 라이브러리 사용경험이 없어서 사용을 해보게되었습니다.
해당 라이브러리를 사용하면서 좋았던점은 풍부한 기능과 variant 주도 컴포넌트 스타일링이 가능한 부분이었습니다.(+ 해당 라이브러리에 릴리즈 주기, 유지보수가 진행이 되는지, 다른 라이브러리와의 충돌문제 또는 버저닝이슈가 있는지등도 확인을 해보았습니다)
import { styled } from '@stitches/react';
// or
import { createStitches } from "@stitches/react";
export const { styled } = createStitches({
media: {
xs: "(min-width: 360px)",
sm: "(min-width: 640px)",
md: "(min-width: 768px)",
lg: "(min-width: 1024px)"
}
});
styled
객체를 import 하여 스타일드 컴포넌트 작성이 가능합니다.(첫번째 인자로 element 명을 문자열로 받으며, 두번째 인자로는 style 객체를 받습니다.)
createStitches
객체를 import 하여 media 속성을 정의한 styled 객체를 생성하여 반응형에 대해서도 작업이 가능합니다.
선언한 반응형에 대한 속성에 접근을 하려면 스타일드 컴포넌트내에서 @{media object property name}
으로 사용이 가능합니다. (@xs
, @sm
..)
아래에 @stitches/react 를 활용하여 간단한 버튼 컴포넌트를 작성해보도록 하겠습니다.
// stitchesUtils.ts
import { createStitches } from "@stitches/react";
export const { styled } = createStitches({
media: {
xs: "(min-width: 360px)",
sm: "(min-width: 640px)",
md: "(min-width: 768px)",
lg: "(min-width: 1024px)"
}
});
import { styled } from "../../utils/stitchesUtils";
import type * as Stitches from "@stitches/react";
const ButtonStyled = styled("button", {
appearance: "none",
border: "0px",
borderRadius: "999px",
variants: {
color: {
primary: {
backgroundColor: "#1976d2",
color: "white"
},
violet: {
backgroundColor: "blueviolet",
color: "white"
}
},
size: {
md: {
fontSize: "16px",
height: "36px",
padding: "0 16px"
},
lg: {
fontSize: "18px",
height: "46px",
padding: "0 22px"
}
},
disabled: {
true: {
backgroundColor: "#e6e6e6",
color: "rgba(0, 0, 0, 0.7)",
pointerEvents: "none"
}
}
},
defaultVariants: {
color: "primary",
size: "md"
}
});
type ButtonStyledVariantsType = Stitches.VariantProps<typeof ButtonStyled>;
interface IButtonProps {
variants?: string;
disabled?: boolean;
size?: string;
label?: string;
handleClick?(): void;
}
export default function Button({
variants = "primary",
disabled,
size,
label = "",
handleClick
}: IButtonProps) {
const onClick = () => {
if (handleClick) handleClick();
};
return (
<ButtonStyled
color={variants as ButtonStyledVariantsType["color"]}
size={size as ButtonStyledVariantsType["size"]}
disabled={disabled}
onClick={onClick}
>
{label}
</ButtonStyled>
);
}
@stitches/react 의 styled API 에서 두번째 인자는 style 객체를 받는다고 이야기하였습니다. 이 해당 객체에는 variant API 사용이 가능합니다.
const Button = styled('button', {
// base styles
variants: {
color: {
violet: {
backgroundColor: 'blueviolet',
color: 'white',
'&:hover': {
backgroundColor: 'darkviolet',
},
},
gray: {
backgroundColor: 'gainsboro',
'&:hover': {
backgroundColor: 'lightgray',
},
},
},
},
});
() => <Button color="violet">Button</Button>;
variant API 를 활용하여 컴포넌트에 다양한 스타일링이 가능하며, 컴포넌트 스타일링에 유연성과 좋은 유지보수성을 제공해줍니다. + variant 를 통해서 스타일링 관심사 분리를 명확하게 가져갈수 있습니다.
// https://stitches.dev/docs/typescript
import type * as Stitches from '@stitches/react';
그리고 typescript 에 대해 타입을 제공해줘서 바로 사용이 가능합니다.
저는 위 예제코드에서 Stitches.VariantProps
타입을 활용해서 스타일드 컴포넌트에 variant 타입을 type alias 형태로 선언하여 사용하였습니다.
codesandbox : https://codesandbox.io/s/stitches-react-example-hxyrfz
@charset "UTF-8";
.buttonBasic {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
background-color: #0a8fdc;
color: #ffffff;
border : none;
appearance: none;
outline: none;
border-radius: 8px;
font-size: 16px;
padding : 10px 16px;
cursor: pointer;
transition: background-color 0.1s ease-in;
@media (hover: hover) {
&:hover {
background-color: #07649a;
}
}
}
.buttonBasicFullWidth {
width: 100%;
}
.buttonBasicFlat {
box-shadow: none;
}
// ButtonBasicComponent.tsx
import classes from './ButtonBasicComponent.module.scss';
import clsx from 'clsx';
interface IButtonBasicComponentProps {
name?: string;
fullWidth?: boolean;
handleClick?(): void;
flat?: boolean;
}
export default function ButtonBasicComponent({
name = '',
fullWidth = false,
handleClick,
flat = false,
}: IButtonBasicComponentProps) {
const onClick = () => {
if (handleClick) handleClick();
};
return (
<button
className={clsx(classes.buttonBasic, fullWidth && classes.buttonBasicFullWidth, flat && classes.buttonBasicFlat)}
type="button"
onClick={onClick}
>
{name}
</button>
);
}
이제는 스타일드 컴포넌트 뽕맛을 보아서 위처럼 작업을 하는게 꺼려질거같다..
저기에 BEM 표기법으로 CSS 구조를 잡아가면 그거는 그거대로 지옥이 열리고, 문제는 className 에 props 에 따라 해당하는 클래스를 적절히 적용해줘야하는데 잘못 작성하면 참 지저분해진다.