리액트에서 컴포넌트를 스타일링하는 다양한 방법이 존재한다. 어떠한 방식들이 있는지 알아보도록 하겠다.
CSS는 1996년에 도입된 이래와 같이 발전해왔다.
(oliverturner's twitter (opens in a new tab))
일반 CSS
- 기존의 css를 사용하는 방식
- css클래스가 중복되지 않게 하기 위해 규칙을 사용하여 이름을 짓거나 css selector를 활용할 수 있다. (충돌 방지)
- 규칙
- ‘컴포넌트이름-클래스’ 형태
- 어디에서 어떤 용도로 사용되는지 명확히 작성하는 방식
- css selector
- css 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있도록 함
- Ex: “.App header”, “.App .logo"
- 규칙
Sass
- Syntactically Awesome Style Sheets (sass 언어, 전처리기)
- sass는 css코드를 간결하고 효율적으로 작성하기 위해 사용하는 프로그래밍 언어이다.
- sass로 코드를 작성하면 sass 전처리가 이를 css로 컴파일해 브라우저에 로드될 수 있도록 빌드해 준다.
- .scss와 .sass의 두 가지 확장자 지원
- 두 확장자의 문법은 아래와 같은 차이가 있다.
// .sass
$font-stack: Helvetica, sans-serif
$primary-color: #333
body
font: 100% $font-stack
color: $primary-folor
//.scss
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-folor;
}
sass는 중괄호와 세미콜론을 사용하지 않는다. scss가 css와 작성법이 비슷하여 더 자주 사용된다.
CSS Module
- 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해 주는 옵션
- 파일이름클래스이름**해시값 형태로 이름을 자동으로 변경해줌
- .module.css 확장자로 파일을 저장하면 자동으로 CSS Module이 적용됨
- 특정 클래스를 전역적으로 사용하려면 :global 을 앞에 입력해준다.
- sass도 css module로 사용 가능
장점
- css module을 불러온 컴포넌트 내에서면 작동하기 때문에 클래스 이름을 지을 때 고유성에 대해 생각하지 않아도 된다.
CSS-in-JS
JS 파일 안에 스타일을 선언하는 방식
대표적 예시 : emotion, Styled-components
장점
- css모델을 문서 레벨이 아닌 컴포넌트 레벨로 추상화한다. (모듈성)
- js와 상태를 공유할 수 있다
- 자바스크립트와 css 사이의 상수, 함수를 공유 (ex: props를 활용한 조건부 스타일링)
- 현재 사용중인 스타일만 DOM에 포함한다
- 짧은 길이의 고유한 클래스를 자동으로 생성 → 코드 경량화
단점
- 별도의 라이브러리 설치 → 번들 크기가 커짐
- runtime에 스타일을 생성하는 방식 → 스타일이 복잡한 경우 런타임 성능을 떨어뜨린다.
zero-runtime css-in-js
우리가 흔히 사용하는 runtime-css-in-js의 문제를 해결하기 위해 zero-runtime 라이브러리들이 등장했다.
zero-runtime이란 말 그대로 앞서 설명한 runtime에서의 동작이 없다는 것을 뜻한다. 즉 동적으로 스타일을 생성하지 않는다.
그 중 하나인 Linaria (opens in a new tab)는 styled-components에서 영감을 받아 유사한 API를 가진 CSS-in-JS라이브러이고, zero-runtime으로 동작한다. linaria - how it works (opens in a new tab)에서 설명하는대로 babel plugin과 webpack loader를 통해 사용된 css코드를 추출해 정적인 스타일시트를 생성한다.
CSS-in-CSS vs CSS-in-JS
CSS-in-CSS: 이 방법은 스타일 시트가 별도로 로드되며, 브라우저가 CSSOM (CSS Object Model)을 생성한다. CSSOM이 한 번 생성되고 나면, 다시 렌더링 될 때는 해당 스타일 시트를 다시 파싱할 필요가 없으므로 렌더링 성능이 향상될 수 있다. 또한, 렌더링이 빈번하게 발생하더라도 스타일이 변경되지 않는다면, CSS-in-CSS 방식이 더 효율적일 수 있다.
CSS-in-JS: 이 방법은 컴포넌트가 렌더링 될 때마다 새로운 스타일을 동적으로 생성한다. 따라서, 상태 변경이나 prop 변경에 따라 스타일이 자주 변경되는 컴포넌트의 경우 CSS-in-JS가 더 효율적일 수 있다.
→ 빈번한 리렌더링이 발생하더라도 스타일이 자주 변경되지 않는다면 CSS-in-CSS가 성능적으로 더 유리하고, 스타일이 빈번하게 변경되는 경우 CSS-in-JS가 더 적합할 수 있다.
출처:
https://so-so.dev/web/css-in-js-whats-the-defference/ (opens in a new tab)
https://velog.io/@sunfkkc/CSS-in-JS-vs-CSS-in-CSS-성능-비교를-중심으로 (opens in a new tab)
Styled-components
install
npm install --save styled-components
typeScript를 사용하기 때문에 타임을 가져온다
npm i --save-dev @types/styled-components
styled-components 를 import 해준다
import styled from 'styled-components';
사용 예시
styled.
다음과 같이 컴포넌트를 만들고 ``안에 스타일을 작성해준다. 그리고 export해주면 컴포넌트처럼 사용할 수 있다!
import styled from '@emotion/styled';
const Wrapper = styled.div`
height: 50px;
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem /* 24px */;
`;
다음과 같이 작성해주면 Wrapper라는 스타일을 가진 컴포넌트가 만들어진다.
'use client';
import styled from '@emotion/styled';
const Wrapper = styled.div`
height: 50px;
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem /* 24px */;
`;
export default function NavBar(): JSX.Element {
return (
<Wrapper>
<div>예시</div>
</Wrapper>
);
}
styled component에 props를 넘기거나 상속을 하는 것 또한 가능하다.
Styled-components’s pros
- 스타일과 컴포넌트를 한번에 관리해서 컴포넌트별로 스타일 할 수 있다. 관리가 쉽다.
- 프롭스를 받을 수 있어서 props, state값에 따라 동적으로 스타일링 할 수 있다.
- css클래스 이름 충돌을 방지한다. 스타일을 컴포넌트 범위로 유지하기 때문이다.
이런 Styled-components를 tailwind css로도 만들 수 있다.
이를 위해서 tailwind CSS를 설치하는 과정, 장단점 그리고 tailwind-styled-component를 사용하는 법까지 차근차근 해보자.
Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
파일 안에 tailwind 를 사용할 경로 추가해주기
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}', // Note the addition of the `app` directory.
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
// Or if using `src` directory:
'./src/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
};
global.css에 다음 Tailwind의 directive를 추가한다.
@tailwind base; 는 기본 css를 모두 초기화 한다.
@tailwind base;
@tailwind components;
@tailwind utilities;
Tailwind’s pros
- 빠른 개발
이미 만들어진 클래스를 가져다 쓴다. 이를 element에 바로 적용을 할 수 있기 때문에 빠르고 비교적 쉽게 개발이 가능하다. 또한 사전 정의된 클래스를 조합해서 사용하므로 따로 네이밍 할 필요도 없
- 커스터마이징 쉬움:
기본적으로 제공되는 클래스 외에도 프로젝트에 맞게 새로운 클래스를 정의하거나 기존 클래스를 조합해서 사용 할 수 있다.
- Colocation Bad 해결
기존 css 에 클래스 정의하면 이 스타일에 뭐가 정의되어 있는지 찾아야함. 스타일 보려면 css파일을 봐야한다는 단점이 있음. → Seperation of Concerns
하지만 Tailwind는 딱 보면 뭐가 적용됐는지 파악할 수 있고 수정 삭제 또한 용이하다
- tailwind → Bootstrap 과 css사이
- Bootstrap : Convention + CSS: Configuration
- → desigm system 만들기 쉽다
Tailwind’s cons
- 가독성
필요한 클래스들을 조합하는 방식이기 때문에 클래스를 많이 나열하게 되면 가독성이 떨어진다.
- 러닝 커브
처음에는 클래스 이름을 익히고 이해하는 데 시간이 걸릴 수 있으며, 개발자들이 이에 익숙해지려면 약간의 학습이 필요하다.
tailwind-styled-component
tailwind css로 styled-component 를 만드는 것이다.
다음 명령어로 설치해준다.
npm install -D tailwind-styled-components
그리고 사용하고자 하는 파일에 임포트 해준다.
import tw from "tailwind-styled-components"
import tw from 'tailwind-styled-components';
const MenuItem = tw.a`
text-center
group
hover:border-b-2
border-transparent
hover:border-gray-500
`;
export default function NavBar(): JSX.Element {
return (
<Wrapper>
<MenuItem href="#">채용</MenuItem>
<MenuItem href="#">이벤트</MenuItem>
<MenuItem href="#">이력서</MenuItem>
<MenuItem href="#">소셜</MenuItem>
<MenuItem href="#">프리랜서</MenuItem>
<MenuItem href="#">AI 합격예측</MenuItem>
</Wrapper>
);
}
다음과 같이 a 태그마다 작성해줘야 했던것을
<a href="" className="text-center group hover:border-b-2 border-transparent hover:border-gray-500">채용</a>
<a href="" className="text-center group hover:border-b-2 border-transparent hover:border-gray-500">이벤트</a>
<a href="" className="text-center group hover:border-b-2 border-transparent hover:border-gray-500">이력서</a>
...
컴포넌트화 해서 tailwind를 사용하면서 styled-component 처럼 사용할 수 있다
<MenuItem href="#">채용</MenuItem>