리액트에서 컴포넌트를 스타일링 할 땐느 다양한 방식이 있다. 여러 방식 중에 딱히 정해진 방식은 없다. 회사마다 요구하는 스펙이 다르고, 개발자마다 각자 취향에 따라 선택하기 때문이다.
방식은 크게 4가지가 있다.
1. 일반 CSS : 컴포넌트를 스타일링 하는 가장 기본적인 방식
2. SASS : 자주 사용되는 CSS 전처리기(pre-processor) 중 하나로 확장된 CSS 문법을 사용하여 CSS 코드를 더욱 쉽게 작성할 수 있음
3. CSS Module : 스타일을 작성할 때 CSS 클래스가 다른 CSS클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성해주는 옵션이다.
4. styled-components : 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들수 있게 한다.
1. 가장 흔한 방식, 일반 CSS
프로젝트는 일반 CSS 방식으로 만들어져 있다. 실제로도 소규모 프로젝트를 개발하고 있다면 새로운 스타일링 시스템을 적용하는 것이 불필요 할 수도 있다. 프로젝트에 이미 적용되어 있는 기본 CSS 시스템을 사용하는 것만으로도 충분함
CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것이다.
CSS 클래스가 중복되는 것을 방지하는 여러가지 방식이 있는데 그 중 하나는 이름을 지을 때 특별한 규칙을 사용하여 짓는 것이고, 또 다른 하나는 CSS Selector를 활용하는 것이다.
2. 이름 짓는 규칙
프로젝트에 자동 생성된 App.css를 읽어보면 클래스 이름을 컴포넌트 이름-클래스 형태로 지어져 있음(App-header)
클래스 이름에 컴포넌트 이름을 포함시킴으로써 다른 컴포넌트에서 실수로 중복되는 클래스를 만들어 사용하는 것을 방지할 수 있다.
비슷한 방식으로 BEM(BEM Naming)이라는 방식도 있다. BEM 네이밍은 CSS 방법론 중 하나로, 이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식이다. 예를 들어 .card__title-primary
3. CSS Selector
CSS Selector를 사용하던 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일 적용할 수 있음
.App .logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
}
이렇게 작성하게 되면 .App 안에 들어 있는 .logo에 스타일을 적용하는 경우이다.
이런식으로 컴포넌트의 최상위 html요소에는 컴포넌트의 이름으로 클래스 이름을 짓고(.App) 그 내부에서는 소문자를 입력하거나 (.logo) header 같은 태그를 사용하여 클래스 이름을 불필요한 경우에는 아예 생략할 수 있음
// .App 안에 들어있는 header
// header 클래스가 아닌 header 태그 자체에 스타일을 적용하기 때문에 .이 생략됨
.App header {
background-color : #282c34;
}
// .App 안에 들어 있는 a 태그
.App a {
color : #61dafb;
}
4. Sass 사용하기
Sass(Syntactically Awesome Style Sheets)는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있다. 스타일 코드의 재활용성을 높여줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 더 쉽게 한다.
Sass에서는 두가지 확장자 .scss 와 .sass를 지원함
Sass가 처음 나왔을 때는 .sass 확장자만 지원되었으나 나중에 개발자들의 요청에 의해 .scss 확장자도 지원하게 되었다.
.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 확장자는 중괄호({})와 세미콜론(;)을 사용하지 않는다.
반면 .scss 확장자는 기본 CSS를 작성하는 방식과 비교해서 문법이 크게 다르지 않는다(보통 scss가 더 자주 사용됨)
Sass를 사용하려면 우선 node-sass라는 라이브러리를 설치해주어야 한다.
이 라이브러리는 Sass를 CSS로 변환해줌
yarn add node-sass
SassComponent.css 예시
//변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
//믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용할 수 있음)
@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 {
background: $orange;
@include square(2);
}
&.yellow {
background: $yellow;
@include square(3);
}
&.green {
background: $green;
@include square(4);
}
&.blue {
background: $blue;
@include square(5);
}
&.indigo {
background: $indigo;
@include square(6);
}
&.violet {
background: $violet;
@include square(7);
}
&:hover {
// .box에 마우스를 올렸을 때
background: black;
}
}
}
5. utils 함수 분리하기
여러 파일에서 사용될 수 있는 Sass 변수 및 믹스인은 다른 파일로 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용할 수 있다.
현재 src 폴더 안에 styles 폴더를 만들고 css파일을 다 옮겨 놓은 상태
/utils.scss
//변수 사용하기
$red: #fa5252;
$orange: #fd7e14;
$yellow: #fcc419;
$green: #40c057;
$blue: #339af0;
$indigo: #5c7cfa;
$violet: #7950f2;
//믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용할 수 있음)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
//SassComponent.scss
@import "./styles/utils";
.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 {
background: $orange;
6. Sass-loader 설정 커스터마이징 하기
이 작업은 sass를 사용할 때 반드시 해야 하는 것은 아니지만 해두면 유용하다.
만약 프로젝트에 디텍터리를 많이 만들어서 구조가 깊어졌다면 해당 파일에서는 상위 폴더로 한참 거슬러 올라가야 한다 라는 단점이 있다.
이 문제점은 웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하여 해결할 수 있음
create-react-app으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위해 세부 설정이 모두 숨겨져 있음
이를 커스터마이징하려면 프로젝트 디렉터리에서 yarn eject 명령어를 통해 세부 설정을 밖으로 꺼내 주어야 함
create-react-app에서는 Git 설정이 되어 있는데 yarn eject는 아직 Git에 커밋되지 않은 변화가 있다면 진행되지 않으니 먼저 커밋을 해줘야 한다.
yarn eject
react-scripts eject
하고 나면 프로젝트 디텍터리에 config.j라는 디텍터리가 생성된다.
디텍터리 안에 들어있는 webpack.config.js를 열어서 'sassRegex' 라는 키워드를 찾음
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
여기서 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"],
},
sourceMap: isEnvProduction && shouldUseSourceMap,
},
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
설정 파일을 저장한 후 서버를 껐다가 재시작하면 이제 utils.scss 파일을 불러올 때 현재 수정하고 있는 scss파일 경로가 어디에 위치하더라도 앞부분에 상대 경로를 입력할 필요 없이 styles 디텍터리 기준 절대 경로를 사용하여 불러올 수 있음
@import "utils.scss"
새 파일을 생성할 때마다 utils.scss를 매번 포함시키는 것도 귀찮을 수도 있다.
그럴 때는 sass-loader의 data 옵션을 설정하면 됨
data옵션을 설정하면 Sass파일을 불러올 때마다 코드의 맨 윗부분에 특정 코드를 포함시켜줌
webpack.config.js를 열어서 조금 전 수정했던 sass-loader의 옵션에 있는 data 필드를 다음과 같이 설정하면 됨
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders({
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
}).concat({
loader: require.resolve("sass-loader"),
options: {
sassOptions: {
includePaths: [paths.appSrc + "/styles"],
},
sourceMap: isEnvProduction && shouldUseSourceMap,
prependData: `@import 'utils';`,
},
}),
sideEffects: true,
},
이렇게 작성하고 개발 서버를 재시작하고 나면 모든 scss 파일에서 utils.scss 를 자동으로 불러오므로 Sass에서 맨 윗 줄에 있는 import 구문을 지워도 정상적으로 작동함
여기서 오류가 발생한다면 prependData를 additionalData로 변경하면 됨
7. node_modules에서 라이브러리 불러오기
Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있음
yarn을 통해 설치한 라이브러리를 사용하는 가장 기본적인 방법은 상대 경로를 사용하여 node_modules까지 들어가서 불러오는 것임
@import '../../../node__modules/library/styles';
이런 구조의 문제는 스타일 파일이 깊숙한 디텍터리에 위치할 경우 ../를 많이 적어야 하니 번거로움
이런 경우 더 쉬운 방법이 있는데 바로 물결문자(~)를 사용하는 방법
@import '~library/styles';
물결 문자를 사용하면 자동으로 node-modules에서 라이브러리 디텍터리를 탐지하여 스타일을 불러올 수 있음
yarn add open-color include-media
include-media는 반응형 디자인을 쉽게 만들어주고 open-color를 편리한 색상 팔레트다.
다운을 하고나면 원하는 곳에 Import 해주면 되는데 Sass 라이브러리를 불러올 때는 node_modules 내부 라이브러리 경로 안에 들어 있는 scss파일을 불러와야 한다. 보통 scss vk파일 경로가 어디에 위치하는 지 라이브러리의 공식 메뉴얼에서 알려주지 않을 때가 많으니 직접 경로를 들어가서 확인하는 것이 좋음
//utils.scss
@import "~include-media/dist/include-media";
@import "~open-color/open-color";
//SassComponent.scss
.SassComponent {
display: flex;
background: $oc-gray-2;
@include media("<768px") {
background: $oc-gray-9;
}
8. CSS Module
CSS Modules은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일 이름]_[클래스 이름]_[해시 값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이르이 중첩되는 현상을 방지해 주는 기술임
CSS Modules을 사용하기 위해 구버전은 웹팩에서 css-loader 설정을 별도로 해주어야 했지만 2v버전 이상부터는 따라 설정할 필요 없이 .modules.css 확장자로 파일을 저장하기만 하면 CSS Modules이 적용됨
CSS Modules을 사용하면 클래스 이름을 지을 때 그 고유성에 대해 고민하지 않아도 된다.
해당 클래스는 스타일을 직접 불러온 내부에서만 작용하기 때문에 만약 특정 클래스가 웹페이지에서 전역적으로 사용되는 경우라면 :global을 앞에 입력하면 글로벌 CSS임을 명시
/* CSSModule.module.css */
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
:global .something {
font-weight: 800;
color: aqua;
}
//CSSModule.js
import React from "react";
import styles from "./styles/CSSModule.module.css";
const CSSModule = () => {
return (
<div className={styles.wrapper}>
안녕하세요. 저는<span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
CSS Module이 적용된 스타일 파일을 불러오면 객체를 하나 전달받게 되는데 CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키 - 값 형태로 들어있음
고유한 클래스 이름을 사용하려면 클래스를 적용하고 싶은 JSX 엘리먼트에 className={styles.[클래스이름]} 전달해주면 된다.
:global을 사용하여 전역적으로 선언한 클래스의 경우 평상이 해왔던 것처럼 그냥 문자열로 넣어주면 된다.
CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때는 이렇게 사용하면 된다.
<div className={` ${styles.wrapper} ${styles.inverted}`}>
ES6 문법 템플릿 리터럴을 사용하여 문자열을 합해 준것
이 문법을 사용하여 문자열 안에 JS 레퍼런스를 쉽게 넣어 줄 수 있음
다만 CSS Module 클래스를 여러개 사용할 때 템플릿 리터널 문법을 사용하고 싶다면 이렇게 작성하면 된다.
<div className={[styles.wrapper, styles.inverted].join('')}>
9. classnames
classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리
또한 CSS Module을 사용할 때 이 라이브러리를 사용하면 여러 클래스를 적용할 때 매우 편리하다.
라이브러리 설치
yarn add classnames
classnames는 여러 가지 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있끼
때문에 컴포넌트에서 조건부로 클래스 설정할 때 매우 편리하다(가독성이 높아짐)
또한 CSS Module과 함께 사용하면 CSS Module 사용이 훨씬 쉬워짐
classnames에 내장되어 있는 bind함수를 사용하면 클래스를 넣어줄때마다 styles.[클래스 이름]형태를 사용할 필요가 없다.
사전에 미리 styles에서 받아 온 후 사용하게끔 설정해두고 cx('클래스 이름', '클래스 이름') 형태로 사용할 수 있음
import React from "react";
import styles from "./styles/CSSModule.module.css";
import classNames from "classnames/bind";
const cx = classNames.bind(styles); //미리 styles에서 클래스를 받아 오도록 설정
const CSSModule = () => {
return (
<div className={cx('wrapper', 'inverted')}>
안녕하세요. 저는<span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
CSS Module을 사용할 때 클래스를 여러 개 설정하거나 조건부로 클래스를 설정할 때 classnames의 bind를 사용하면 훨씬 편리하게 작성할 수 있음
10. Sass와 함께 사용하기
Sass를 사용할 때도 파일 이름 뒤에 .module.scss 확장자를 사용해 주면 CSS Module로 사용할 수 있음
11. CSS Module이 아닌 파일에서 CSS Module 사용하기
CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용했던 것처럼 CSS Module이 아닌 일반 .css/.scss파일에서도 :local을 사용하여 CSS Module을 사용할 수 있음
:local .wrapper {
/* 스타일 */
}
:local {
.wrapper {
/* 스타일 */
}
}
12. styled-components
컴포넌트 스타일링의 또 다른 패러다임은 JS파일안에 스타일을 선언하는 방식. 이 방식을 CSS-in-JS라고 부른다.
그 중 개발자들이 가장 선호하는 방식인 styled-components이다.
라이브러릿 설치
yarn add styled-components
styled-components를 사용하면 JS 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있다.
import React from "react";
import styled, { css } from "styled-components";
const Box = styled.div`
/* props로 넣어 준 값을 직접 전달해 줄 수 있음 */
background: ${(props) => props.color || "blue"};
padding: 1rem;
display: flex;
`;
const Button = styled.button`
background: white;
color: black;
border-radius: 4px;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
font-size: 1rem;
font-weight: 600;
&:hover {
background: rgba(255, 255, 255, 0.9);
}
${(props) =>
props.inverted &&
css`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`}
& + button {
margin-left: 1rem;
}
`;
const StyledComponent = () => {
return (
<Box color="black">
<Button>안녕하세요</Button>
<Button inverted={true}>안녕하세요</Button>
</Box>
);
};
export default StyledComponent;
styled-components와 일반 classname를 사용하는 CSS/Sass를 비교했을 때 가장 큰 장점은 props 값으로 전달해주는 값을 쉽게 스타일에 적용할 수 있다는 것
13. Tagged 템플릿 리터럴
스타일을 작성할 때 `을 사용하여 만든 문자열에 스타일 정보를 넣준다.
여기서 사용한 문법을 Tagged 템플릿 리터럴 이라고 부른다.
CSS Module을 배울 때 나온 일반 템플릿 리터럴과 다른 점은 템플릿 안에 JS 객체나 함수를 전달할 때 온전히 추출할 수 있다.
`hello ${{foo: 'bar'}} ${() =? 'world'}!`
//"hello [object Object] () => 'world'!"
템플릿에 객체를 넣거나 함수를 넣으면 형태를 잃어버리게 된다.
객체는 "[object Object]" 로 변환되고 함수는 함수 내용이 그대로 문자열화 되어 버린다.
하지만 함수를 작성하고 해당 함수 뒤에 템플릿 리터럴을 넣어 주며 템플릿 안에 넣은 값을 온전히 추출할 수 있다.
function taggeed(...args){
console.log(args);
}
`hello ${{foo: 'bar'}} ${() =? 'world'}!`
//(3) [Array(3), {...}, f]
Tagged 템플릿 리터럴을 사용하면 템플릿 사이사이에 들어가는 JS객체나 함수의 원본 값을 그대로 추출할 수 있다.
styled-components는 이러한 속성을 사용하여 styled-components로 만든 컴포넌트의 props를 스타일 쪽에 쉽게 조회 할 수 있도록 해준다.
14. 스타일링 된 엘리먼트 만들기
styled-components를 사용하여 스타일링된 엘리먼트를 만들 때는 컴포넌트 파일의 상단에서 styled를 불러오고 styled.태그명을 사용하여 구현한다.
import styled from "styled-components";
const MyComponent = styled.div`
font-size: 2rem;
`
이렇게 styled.div 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어주면 해당 스타일이 적용한 div로 이루어진 리액트 컴포너트가 생성된다. div가 아닌 다른 태그명을 원한다면 div 위치에 다른 태그명을 넣어주면 된다.
하지만 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링 해주고 싶다면 이런 형태로 구현한다.
//태그 타입을 styled 함수의 인자로 전달
const MyInput = styled('input')`
background: grey;
`
//아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)`
color: blue;
`
15. 스타일에서 props 조회하기
styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있다.
const Box = styled.div`
background: ${(props) => props.color || "blue"};
padding: 1rem;
display: flex;
`;
<Box color="black">(...)</Box>
16. props에 따른 조건부 스타일링
일반 CSS 클래스를 사용하여 조건부 스타일링 할 때는 classname을 사용하여 조건부 스타일링을 했는데 styled-components에서는 조건부 스타일링을 간단하게 props로도 처리할 수 있다.
${(props) =>
props.inverted &&
css`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`}
스타일 코드 여러줄을 props에 따라 넣어주어야 할 때는 CSS를 styled-components에서 불러와야 한다.
CSS를 사용하지 않고 문자열을 넣어도 작동함
${(props) =>
props.inverted &&
`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`}
하지만 이렇게 했을 때는 해당 내용이 그저 문자열로만 취급되기 때문에 VS Code확장 프로그램에서 신택스 하이라이팅이 제대로 이루어지지 않는다는 단점이 있다. 그리고 더욱 치명적인 단점은 Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못해 해당 부분에서는 props 값을 사용하지 못한다는 것! 만약 조건부 스타일링을 할 때 넣는 여러 줄의 코드에서 props를 참조하지 않는 다면 굳이 CSS를 불러와서 사용하지 않아도 상관 없지만 props를 참조한다면 반드시 CSS로 감싸주어서 Tagged 템플릿 리터럴을 사용해주어 함
17. 반응형 디자인
브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media 쿼리를 사용하면 된다.
const Box = styled.div`
/* 기본적으로 가로 크기 1024px에 가운데 정렬을 하고
가로 크기가 작아짐에 따라 크기를 줄이고 768px 미만이 되면 꽉 채움 */
width: 1024px;
margin: 0 auto;
@media (max-width: 1024px) {
width: 768px;
}
@media (max-width: 768px) {
width: 100%;
}
`;
일반 CSS에서 할 때랑 큰 차이가 없다. 하지만 이러한 작업을 여러 컴포넌트에서 반복해야 한다면 조금 귀찮을 수도 있다.
이럴 때에 이 작업을 함수화 하여 간편하게 사용할 수 있다.
const sizes = {
desktop: 1024,
tablet: 768,
};
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`
width: 1024px;
margin: 0 auto;
${media.desktop`width: 768px`}
${media.tablet`width: 100%`}
`;
이 방식을 실제로 사용한다면 media는 아예 다른 파일로 모듈화 한 뒤 여기저기서 불러와 사용하는 방식이 훨씬 편리하다.
18. 정리
이 장에서 배운 스타일링 중 무엇을 선택할지는 나의 몫이다.
'React > 다시 공부하는 리액트' 카테고리의 다른 글
React 기본 개념 - 2 (0) | 2024.11.10 |
---|---|
React 기본 개념 정리 (1) | 2024.11.09 |
다시 공부하는 리액트 8장 Hooks (3) | 2023.07.06 |
다시 공부하는 리액트 7장 컴포넌트의 라이프사이클 메서드 (1) | 2023.07.05 |
다시 공부하는 리액트 6장 컴포넌트 반복 (0) | 2023.07.04 |