여러가지 스타일링 방식
- 일반 CSS
- Sass
- css 전처리기
- CSS Module
- styled-components
일반 CSS
기존 CSS를 사용하는 방식입니다.
App.js
import {Component} from 'react';
import logo form './logo.svg';
import './App.css';
(...)
App.css
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
(...)
이름 짓는 규칙
- 컴포넌트 이름-클래스 형태
- App-header
- BEM 네이밍
- .card_title-primary
CSS selector
CSS가 특정 클래스 내부에 있는 경우메나 스타일을 적용할 수 있습니다.
/* App 안에 있는 logo */
.App .logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
/* .App 안에 들어 있는 header
header 클래스가 아닌 header 태그 자체에
스타일을 적용하기 때문에 .이 생략 되었습니다. */
.App header {
background-color: black;
}
/* App 안에 들어 있는 a 태그 */
.App a {
color: white;
}
Sass
CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해줍니다. 스타일 코드의 재활용성을 높혀주고 코드의 가독성을 높혀 유지보수를 더욱 쉽게 해줍니다.
두 가지 확장자 .scss .sass 를 사용합니다. 허나, 문법은 다릅니다.
.sass
$font-stack: Helvetica, sans-serif
$primary-color: #333
body
font: 100% $font-stack
color: $primary-color
.scss
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-color;
}
실제로 Sass를 작성해봅니다.
SassComponent.scss
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
// 믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사요할 수 있음)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
.SassComponent {
display: flex;
.box { // 일반 CSS에서는 .SassComponent .box 마찬가지
background: red;
cursor: pointer;
transition: all 0.3s ease-in;
&.red {
// .red 클래스가 .box와 함께 사용되었을 때
background: $red;
@include square(1);
}
&.orange {
// .red 클래스가 .box와 함께 사용되었을 때
background: $orange;
@include square(2);
}
(...)
}
}
SassComponent.js
import { Component } from 'react';
import SassComponent from './SassComponent';
const SassComponent = () => {
return(
<div className="SassComponent">
<div className="box red" />
<div className="box orange" />
<div className="box yellow" />
</div>
)
}
이런식으로 작성하면 됩니다!
utils 분리하기
여러 파일에서 사용될 수 있는 Sass 믹스인은 다른 파일로 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용할 수 있습니다.
src/styles/utils.scss
//변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fc419;
// 믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용할 수 있음)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
SassComponent.scss
@import './styles/utils';
.SassComponent {
display: flex;
.box {
background: red;
cursor: pointer;
(...)
}
}
sass-loader 설정 커스터마이징
yarn eject 는 아직 Git에 커밋되지 않은 변화가 있다면 진행되지 않아, 먼저 커밋을 해주어야 합니다.
$ git add .
$ git commit -m 'Commit before yarn eject'
yarn eject 명령어를 실행합니다.
$ yarn eject
$ react-script eject
이렇게 하면 config 디렉토리가 생성됩니다. 그 안에 webpack.config.js를 수정합니다.
"sassRegex" 키워드를 찾습니다.
use:
에 있는 'sass-loader' 부분을 지우고, 뒷부분에 concat을 통해 커스터마이징된 sass-loader 설정을 넣어줍니다.
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders({
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
}).concat({
loader: require.resolve("sass-loader"),
options: {
sassOptions: {
includePaths: [paths.appSrc + "/styles"],
},
},
}),
sideEffects: true;
},
styles 디렉토리 기준 절대경로가 설정이 되었습니다.
import 'utils.scss';
따라서 상대경로로 작성하지 않아도 파일을 참조 가능합니다.
node_modules에서 라이브러리 불러오기
yarn add open-color include-media
utils.scss
@import '~include-media/dist/include-media';
@import '~open-color/open-color';
CSS Module
CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일 이름]_[클래스 이름]_[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술입니다.
CSSModule.module.css
/* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용 가능 */
.wrapper {
background: black;
}
/* 글로벌 css 를 작성하고 싶다면 */
:global . something {
font-weight:800;
}
CSSModule.js
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={styles.wrapper}>
안녕하세요, 저는<span className={something}>CSS</span>
</div>
)
}
여기서 console.log(styles)
하면 결과가
{wrapper: "CSSModule_wrapper_1sbdQ"}
처럼 고유한 값이 들어있습니다.
Styled-components
'CSS-in-JS', 자바스크립트 파일 안에 스타일을 선언하는 방식입니다.
대체 라이브러리로는 emotion이 있습니다.
설치
npm install styled-compnents
css 또는 scss 를 따로 생성하지 않아도 되고 바로 작성해주면 됩니다.
StyledComponent.js
import styled, {css} from 'styled-components';
const Box = styled.div`
background: black;
`;
const Button = styled.button`
color: black;
`;
styled.태그명을 사용해서 css를 적용해줍니다.
vscode-styled-components 를 설치하면 코드 신택스 하이라이팅이 적용된다고 합니다!
스타일링 된 엘리먼트 만들기
사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링해 주고 싶다면 다음과 같은 형태로 구현 가능합니다.
// 태그의 타입을 styled의 인자로 전달
const MyInput = styled('input')`
background: gray;
`;
// 아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled('Link')`
color: blue;
`;
스타일에서 props 조회하기
styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있습니다.
const Box = styled.div`
background: ${props => props.color || 'blue'};
`;
props의 값을 조회해서 props.color 값을 사용합니다. 해당 값이 없을 경우 default로 'blue' 가 들어갑니다.
props에 따른 조건부 스타일링
/* 단순 변수의 형태가 아니라 여러 줄의 스타일 구문을 조건부로 설정해야 하는 경우에는 css 를 불러와야 합니다. */
const Box = styled.button`
background: ${props => props.color || 'blue'};
/* & 문자를 사용하여 Sass 처럼 자기 자신 선택 가능 */
&:hover {
background: rgba(1,1,1,1);
}
/* 다음 코드는 inverted 값이 true일 때 스타일을 부여해 줍니다. */
${props => props.inverted &&
css`
background: none;
&:hover {
background:white;
}
`}
& + button {
margin-left:1rem;
}
`;
이렇게 만든 컴포넌트는 props를 사용하여 서로 다른 스타일을 적용할 수 있습니다.
<Button>안녕</Button>
<Button inverted={true}>테두리</Button>
CSS를 사용하지 않고 문자열을 넣어도 작동은 합니다!
Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못해 해당 부분에서는 props 값을 사용하지 못한다는 단점이 있습니다.
반응형 디자인
일반 CSS와 동일하게 media 쿼리를 사용해주면 됩니다.
허나 이러한 작업을 여러 컴포넌트에서 반복해야한다면 귀찮을 수도 있습니다. 그럴 때는 이 작업을 함수화하여 간편하게 사용할 수 있습니다.
import styled, {css} from 'styled-components';
const sizes = {
desktop: 1024,
tablet: 768
};
//위에 있는 size 객체에 따라 자동으로 media 쿼리 함수를 만들어 줍니다.
const media = Object.keys(sizes).reduce((acc, label)=>{
acc[label] = (...args) => css`
@media (max-width: ${sizes[label] / 16}em) {
${css(...args)};
}
`;
return acc;
},{});
const Box = styled.div`
background: ${props => props.color || 'blue'};
${media.desktop`width: 768px;`}
${media.tablet`width: 100%;`};
`;
styled-components의 ThemeProvider
테마를 적용하는 helper 컴포넌트 입니다. Context API를 통해 컴포넌트 트리의 어떤 곳에서든 스타일된 컴포넌트에 테마를 주입합니다.
App.js
import React from "react";
import styled, { ThemeProvider } from "styled-components";
import Button from "./components/Button";
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
function App() {
return (
<ThemeProvider
theme={{
palette: {
blue: "#228be6",
gray: "#495057",
pink: "#f06595",
},
}}
>
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
</ThemeProvider>
);
}
export default App;
theme 을 설정하면 ThemeProvider 내부에 렌더링된 styled-components 로 만든 컴포넌트에서 palette 를 조회하여 사용 할 수 있습니다.
components/Button.js
import React from "react";
import styled, { css } from "styled-components";
import { darken, lighten } from "polished";
const StyledButton = styled.button`
(...)
/* 색상 */
${(props) => {
const selected = props.theme.palette.blue;
}}
(...)
`;
(...)
ThemeProvider 로 설정한 값은 styled-components 에서 props.theme 로 조회 할 수 있습니다.
해당 부분을 비구조화하여 가독성을 높힐 수 있습니다.
components/Button.js
import React from "react";
import styled, { css } from "styled-components";
import { darken, lighten } from "polished";
const StyledButton = styled.button`
(...)
/* 색상 */
${({ theme, color }) => {
const selected = theme.palette[color];
}}
(...)
`;
(...)
size props 설정 부분을 분리하기
아래와 같이 코드작성을 하게 되면 유지보수 할 때 더 편리해집니다.
components/Button.js
import React from "react";
import styled, { css } from "styled-components";
import { darken, lighten } from "polished";
const colorStyles = css`
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
`;
const sizes = {
large: {
height: "3rem",
fontSize: "1.25rem",
},
medium: {
height: "2.25rem",
fontSize: "1rem",
},
small: {
height: "1.75rem",
fontSize: "0.875rem",
},
};
const sizeStyles = css`
${({ size }) => css`
height: ${sizes[size].height};
font-size: ${sizes[size].fontSize};
`}
`;
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
${sizeStyles}
/* 색상 */
${colorStyles}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
function Button({ children, color, size, ...rest }) {
return (
<StyledButton color={color} size={size} {...rest}>
{children}
</StyledButton>
);
}
Button.defaultProps = {
color: "blue",
size: "medium",
};
export default Button;
트랜지션 구현
트랜지션 효과를 적용 할 때에는 CSS keyframe을 사용하며, styled-components 에서 이를 사용할 때는 keyframes 라는 유틸을 사용합니다.
import React from "react";
import styled, { keyframes } from "styled-components";
import Button from "./Button";
const fadeIn = keyframes`
from {
opacity: 0
}
to {
opacity: 1
}
`;
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${fadeIn};
animation-fill-mode: forwards;
`;
(...)
Styled-components vs Tailwind CSS 성능과 개발 효율성 비교
Tailwind란?
Tailwind CSS는 Utility-First 컨셉을 가진 CSS 프레임워크입니다. 여러 유틸리티 클래스를 조합하여 스타일을 정의합니다.
<button class="py-2 px-4 rounded-lg shadow-md text-white bg-blue-500">
Click me
</button>
런타임 성능 비교
styled-components는 CSS-in-JS 라이브러리로 런타임에 Javascript를 사용하여 스타일을 적용합니다. 이는 추가적인 Javascript 계산이 필요함을 의미하며, 성능에 영향을 줄 수 있습니다.
Tailwind CSS는 정적 CSS 클래스를 사용하며, 이는 브라우저가 스타일을 더 삐르게 해석할 수 있게 해줍니다.
번들 크기 비교
styled-components는 스타일을 Javascript 객체로 관리하여 번들 크기가 커질 수 있습니다.
Tailwind CSS는 많은 유틸리티 클래스를 생성하지만, PurgeCSS를 사용해 사용하지 않ㅎ는 스타일을 제거하면 번들 크기를 줄일 수 있습니다.
개발 시간 및 유연성
styled-components는 Javascript와 CSS의 결합을 통해 유연한 스타일링을 가능하게 합니다.
Tailwind CSS는 클래스 기반으로 빠른 UI 구성을 지원하지만, 매우 특정한 스타일링이 필요한 경우에는 제한적일 수 있습니다.
캐싱 및 최적화
styled-copmonents는 런타임에 스타일을 생성하기 때문에 캐싱 전략이 어렵습니다.
Tailwind CSS의 정적 스타일 시트는 브라우저 캐싱에 유리하며, 빠른 로딩 시간을 제공합니다.
서버 사이드 렌더링 (SSR)
styled-components는 SSR에 대한 내장 지원이 있지만, 스타일이 없는 콘텐츠의 깜박임을 방지하기 위해 추가 설정이 필요합니다.
Tailwind CSS는 추가 설정이 필요하지 않습니다.
styled-component의 반짝임 이슈
“반짝임” 은 공식적으로 Flash of Unstyled Content(FUOC)라고 불리는 문제입니다. 웹페이지가 외부에서 작성된 CSS Stylesheet를 불러오기 전에, 아주 잠깐동안 브라우저의 디폴트 스타일을 잠깐 보여주는 것입니다.
일반적인 브라우저 렌더링 과정에 대해 생각해보면,
- 브라우저가 HTML을 파싱해서 DOM Tree를 생성하고
- CSS를 프로세싱 해서 CSS Object Model(CSSOM) Tree를 생성하고
- DOM과 CSSOM Tree를 결합해서 Render Tree를 생성하고
- 화면을 그리기 시작합니다.
즉 자바스크립트를 실행하기 전에 스타일이 화면에 적용되는 것입니다. styled component는 스타일링을 자바스크립트가 실행되는 런타임에 적용하기 때문에 자바스크립트가 실행되기 전까지 사용자는 스타일이 적용되지 않은 HTML 만으로 이루어진 DOM Tree를 보게 되는 것입니다.
자바스크립트가 실행되는 시점과 사용자가 화면을 보는 시점을 Client-Side Rendering(CSR)과 Server-Side Rendering(SSR)을 비교해서 알아보겠습니다.
CSR
Client-Side Rendering(CSR)에서는 자바스크립트 파일이 브라우저에서 실행됩니다.
HTML이 로딩 후와 자바스크립트 실행 전 아주 잠깐동안 HTML content에 스타일이 적용되지 않은 상태로 보일 수 있습니다.
하지만, 이 시간이 굉장히 짧기 때문에, 자바스크립트를 파싱하고 실행하는데 어마어마한 시간이 소요되는 경우가 아니라면 "반짝임"을 경험할 가능성은 매우 낮습니다.
SSR
(SSR)에서는 서버에서 브라우저로 전송하는 HTML파일에는 리액트 컴포넌트의 최초 렌더를 포함하고 있습니다.
만약 styled-component가 적용하는 스타일이 서버의 response에 포함되어있지 않는다면, 스타일이 바로 적용되지 않을 수 있습니다.
HTML은 브라우저에서 열리자마자 내용이 보이고, 그 이후에 자바스크립트를 로딩하고, 파싱하고, 실행시키기 때문입니다. CSS-in-JS를 사용하는 경우에는 자바스크립트가 실행되는 런타임에 스타일이 적용되기 때문에, 자바스크립트가 실행되기 전에는 사용자가 스타일이 적용되지 않은 화면을 봐야하는 것입니다.
참고블로그
[React] React에서 Styled-Components로 Styling하기 (opens in a new tab) Styled-Components vs Tailwind CSS: 성능과 개발 효율성 비교 (opens in a new tab) Hello Tailwind CSS! | 장점, 단점, 사용법 (opens in a new tab) Styled Component vs Tailwind CSS (opens in a new tab)