CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것입니다. 이름을 지을 때 특별한 규칙을 사용하거나 CSS Selector를 활용할 수 있습니다.
컴포넌트 이름-클래스 형태로 지어집니다. (ex: App-header) BEM 네이밍은 CSS 방법론 중 하나로, 이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식입니다. (ex: .card__title-primary)
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있습니다.
/* .App 안에 들어 있는 header
header 클래스가 아닌 header 태그 자체에
스타일을 적용하기 때문에 .이 생략되었습니다. */
.App header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
Sass는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해 주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 해 줍니다. Sass에서는 두 가지 확장자 .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를 작성하는 방식과 비교해서 문법이 크게 다르지 않습니다. node-sass라는 라이브러리를 설치해 주면 Sass를 CSS로 변환해 줍니다.
//SassComponent.js
import React from 'react';
import './SassComponent.scss';
const SassComponent = () => {
return (
<div className="SassComponent">
<div className="box red" />
<div className="box orange"/>
<div className="box yellow" />
<div className="box green" />
<div className="box blue" />
<div className="box indigo" />
<div className="box violet" />
</div>
);
};
export default SassComponent;
//SassComponent.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 {
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;
}
}
}
//App.js
import React, { Component } from 'react';
import SassComponent from './SassComponent';
class App extends Component {
render() {
return (
<div>
<SassComponent />
</div>
);
}
}
export default App;
여러 파일에서 사용될 수 있는 Sass 변수 및 믹스인은 다른 파일로 따로 분리하여 작성한 뒤 필요한 곳에서 쉽게 불러와 사용할 수 있습니다.
//src/styles/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;
}
utils.scss 파일에서 선언한 변수와 믹스인을 SassComponent.scss에서 사용해 볼 때 @import 구문을 사용합니다.
//SassComponent.scss
@import './styles/utils';
.SassComponent {
display: flex;
.box {
background: red; // 일반 CSS에서는 .SassComponent .box와 마찬가지
cursor: pointer;
transition: all 0.3s ease-in;
(...)
}
}
Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 점입니다. 상대 경로를 사용하여 node_modules까지 들어가서 불러오는 방법입니다.
@import '../../../node_modules/library/styles';
더 쉬운 방법이 있는데, 바로 물결 문자(~)를 사용하는 방법입니다.
@import '~library/styles';
반응형 디자인을 쉽게 만들어 주는 include-media와 매우 편리한 색상 팔레트인 open-color를 yarn 명령어를 사용해 설치해 보세요.
$ yarn add open-color include-media
CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값, 즉 [파일 이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술입니다.
//CSSModule.module.css
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
:global .something {
font-weight: 800;
color: aqua;
}
CSS Module을 사용하면 클래스 이름을 지을 때 그 고유성에 대해 고민하지 않아도 됩니다. 흔히 사용하는 단어로 이름을 짓는다고 해도 전혀 문제가 되지 않습니다. 해당 클래스는 우리가 방금 만든 스타일을 직접 불러온 컴포넌트 내부에서만 작동하기 때문입니다.
//CSSModule.js
import React from 'react';
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={styles.wrapper}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
CSS Module이 적용된 스타일 파일을 불러오면 객체를 하나 전달받게 되는데 CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어 있습니다.
CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때는 다음과 같이 코드를 작성하면 됩니다.
//CSSModule.module.css
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
.inverted {
color: black;
background: white;
border: 1px solid black;
}
/* 글로벌 CSS를 작성하고 싶다면 */
:global .something {
font-weight: 800;
color: aqua;
}
//CSSModule.js
import React from 'react';
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={`${styles.wrapper} ${styles.inverted}`}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
ES6 문법 템플릿 리터럴(Template Literal)을 사용하여 문자열을 합해 주었습니다.
classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리입니다. 또한, CSS Module을 사용할 때 이 라이브러리를 사용하면 여러 클래스를 적용할 때 매우 편리합니다.
Sass를 사용할 때도 파일 이름 뒤에 .module.scss 확장자를 사용해 주면 CSS Module로 사용할 수 있습니다.
//CSSModule.module.scss
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
&.inverted {
// inverted가 .wrapper와 함께 사용되었을 때만 적용
color: black;
background: white;
border: 1px solid black;
}
}
/* 글로벌 CSS를 작성하고 싶다면 */
:global {
// :global {}로 감싸기
.something {
font-weight: 800;
color: aqua;
}
}
CSSModule.js 상단에서도 .css 파일 대신 .scss 파일을 불러오세요.
import styles from './CSSModule.module.scss';
이전과 똑같은 화면이 나타날 것입니다.
CSS Module에서 글로벌 클래스를 정의할 때 :global을 사용했던 것처럼 CSS Module이 아닌 일반 .css/.scss 파일에서도 :local을 사용하여 CSS Module을 사용할 수 있습니다.
:local .wrapper {
/* 스타일 /
}
:local {
.wrapper {
/ 스타일 */
}
}
CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하는 styled-components를 알아보겠습니다. styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있습니다.
//StyledComponent.js
import React from 'react';
import styled, { css } from 'styled-components';
const Box = styled.div</span>
<span class="co44">/* props</span><span class="co44">로</span> <span class="co44">넣어</span> <span class="co44">준</span> <span class="co44">값을</span> <span class="co44">직접</span> <span class="co44">전달해</span> <span class="co44">줄</span> <span class="co44">수</span> <span class="co44">있습니다</span><span class="co44">. */</span>
<span class="co33">background</span><span class="co34">:</span> <span class="co49">${</span><span class="co33">props</span> <span class="co46">=></span> <span class="co34">props</span><span class="co33">.</span><span class="co34">color</span> <span class="co35">||</span> <span class="co31">'</span><span class="co31">blue</span><span class="co31">'</span><span class="co49">}</span><span class="co36">;</span>
<span class="co33">padding</span><span class="co34">:</span> <span class="co32">1rem</span><span class="co36">;</span>
<span class="co33">display</span><span class="co34">:</span> <span class="co33">flex</span><span class="co36">;</span>
<span class="co31">;
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;
/* & 문자를 사용하여 Sass처럼 자기 자신 선택 가능 */
&:hover {
background: rgba(255, 255, 255, 0.9);
}
/* 다음 코드는 inverted 값이 true일 때 특정 스타일을 부여해 줍니다. */
${props =>
props.inverted &&
css</span>
<span class="co33">background</span><span class="co34">:</span> <span class="co33">none</span><span class="co49">;</span>
<span class="co33">border</span><span class="co34">:</span> <span class="co32">2px</span> <span class="co33">solid</span> <span class="co32">white</span><span class="co49">;</span>
<span class="co33">color</span><span class="co34">:</span> <span class="co32">white</span><span class="co49">;</span>
<span class="co34">&</span><span class="co32">:hover</span><span class="co49"> {</span>
<span class="co33">background:</span> <span class="co32">white</span><span class="co49">;</span>
<span class="co33">color:</span> <span class="co32">black</span><span class="co49">;</span>
<span class="co49"> }</span>
<span class="co31">};
& + button {
margin-left: 1rem;
}
`;
const StyledComponent = () => (
<Box color=“black“>
<Button>안녕하세요</Button>
<Button inverted={true}>테두리만</Button>
</Box>
);
export default StyledComponent;
//App.js
import React, { Component } from 'react';
import StyledComponent from './StyledComponent';
class App extends Component {
render() {
return (
<div>
<StyledComponent />
</div>
);
}
}
export default App;
styled-components와 일반 classNames를 사용하는 CSS/Sass를 비교했을 때, 가장 큰 장점은 props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다는 것입니다.
해당 코드의 결과를 직접 확인하고 싶다면 크롬 브라우저의 개발자 콘솔을 열어서 자바스크립트 콘솔에 위 코드를 붙여 넣어 보세요.
Tagged 템플릿 리터럴을 사용하면 이렇게 템플릿 사이사이에 들어가는 자바스크립트 객체나 함수의 원본 값을 그대로 추출할 수 있습니다. styled-components는 이러한 속성을 사용하여 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해 줍니다.
styled-components를 사용하여 스타일링된 엘리먼트를 만들 때는 컴포넌트 파일의 상단에서 styled를 불러오고, styled.태그명을 사용하여 구현합니다.
import styled from 'styled-components';
const MyComponent = styled.div`
font-size: 2rem;
`;
이렇게 styled.div 뒤에 Tagged 템플릿 리터럴 문법을 통해 스타일을 넣어 주면, 해당 스타일이 적용된 div로 이루어진 리액트 컴포넌트가 생성됩니다. 그래서 나중에 <MyComponent>Hello</MyComponent>와 같은 형태로 사용할 수 있습니다.
하지만 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링해 주고 싶다면 다음과 같은 형태로 구현할 수 있습니다.
// 태그의 타입을 styled 함수의 인자로 전달
const MyInput = styled(‘input‘)</span>
<span class="co33">background</span><span class="co34">:</span> <span class="co32">gray</span><span class="co36">;</span>
<span class="co31">
// 아예 컴포넌트 형식의 값을 넣어 줌
const StyledLink = styled(Link)</span>
<span class="co33">color</span><span class="co34">:</span> <span class="co32">blue</span><span class="co36">;</span>
<span class="co31">
styled-components를 사용하면 스타일 쪽에서 컴포넌트에게 전달된 props 값을 참조할 수 있습니다.
const Box = styled.div</span>
<span class="co44">/* props</span><span class="co44">로</span> <span class="co44">넣어</span> <span class="co44">준</span> <span class="co44">값을</span> <span class="co44">직접</span> <span class="co44">전달해</span> <span class="co44">줄</span> <span class="co44">수</span> <span class="co44">있습니다</span><span class="co44">. */</span>
<span class="co33">background</span><span class="co34">:</span> <span class="cd2 co49">${</span><span class="cd2 co33">props</span> <span class="cd2 co46">=></span> <span class="cd2 co34">props</span><span class="cd2 co33">.</span><span class="cd2 co34">color</span> <span class="cd2 co35">||</span> <span class="cd2 co31">'</span><span class="cd2 co31">blue</span><span class="cd2 co31">'</span><span class="cd2 co49">}</span><span class="cd2 co36">;</span>
<span class="co33">padding</span><span class="co34">:</span> <span class="co32">1rem</span><span class="co36">;</span>
<span class="co33">display</span><span class="co34">:</span> <span class="co33">flex</span><span class="co36">;</span>
<span class="co31">;
이 코드를 보면 background 값에 props를 조회해서 props.color의 값을 사용하게 했습니다. 그리고 color 값이 주어지지 않았을 때는 blue를 기본 색상으로 설정했습니다. JSX에서 사용될 때 다음과 같이 color 값을 props로 넣어 줄 수 있습니다.
styled-components에서는 조건부 스타일링을 간단하게 props로도 처리할 수 있습니다. 스타일 코드 여러 줄을 props에 따라 넣어 주어야 할 때는 CSS를 styled-components에서 불러와야 합니다. CSS를 사용하지 않고 다음과 같이 바로 문자열을 넣어도 작동하기는 합니다.
${props =>
props.inverted &&
`
background: none;
border: 2px solid white;
color: white;
&:hover {
background: white;
color: black;
}
`};
props를 참조한다면, 반드시 CSS로 감싸 주어서 Tagged 템플릿 리터럴을 사용해 주어야 합니다.
브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media 쿼리(query)를 사용하면 됩니다.
//StyledComponents.js - Box
const Box = styled.div`
/* props로 넣어 준 값을 직접 전달해 줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
/* 기본적으로는 가로 크기 1024px에 가운데 정렬을 하고
가로 크기가 작아짐에 따라 크기를 줄이고
768px 미만이 되면 꽉 채웁니다. */
width: 1024px;
margin: 0 auto;
@media (max-width: 1024px) {
width: 768px;
}
@media (max-width: 768px) {
width: 100%;
}
`;
일반 CSS에서 할 때랑 큰 차이가 없습니다. 그런데 이러한 작업을 여러 컴포넌트에서 반복해야 한다면 조금 귀찮을 수도 있습니다. 그럴 때는 이 작업을 함수화하여 간편하게 사용할 수 있습니다. styled-components 매뉴얼에서 제공하는 유틸 함수를 따라 사용해 보면 다음과 같습니다.
import React from 'react';
import styled, { css } from 'styled-components';
const sizes = {
desktop: 1024,
tablet: 768
};
// 위에 있는 size 객체에 따라 자동으로 media 쿼리 함수를 만들어 줍니다.
// 참고: https://www.styled-components.com/docs/advanced#media-templates
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`
/* props로 넣어 준 값을 직접 전달해 줄 수 있습니다. */
background: ${props => props.color || 'blue'};
padding: 1rem;
display: flex;
width: 1024px;
margin: 0 auto;
${media.desktop`width: 768px;`}
${media.tablet`width: 100%;`};
`;
media를 한번 선언하고 나니까 이를 사용할 때 스타일 쪽의 코드가 훨씬 간단해진 것을 볼 수 있습니다. 지금은 media를 StyledComponent.js에서 만들어 주었지만, 실제로 사용한다면 아예 다른 파일로 모듈화한 뒤 여기저기서 불러와 사용하는 방식이 훨씬 편할 것입니다.
Quiz
1. CSS 이름을 지을 때 특별한 규칙을 사용하거나 CSS Selector를 활용할 수 있습니다.
2. 이름 짓는 규칙은 컴포넌트 이름-클래스 형태로 지어집니다.
3. Sass는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해 주고, 스타일 코드의 재활용성을 높여 줄 뿐만 아니라 코드의 가독성을 높여서 유지 보수를 더욱 쉽게 해 줍니다.
4. Sass에서는 두 가지 확장자 .scss와 .sass를 지원합니다.
5. CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 [파일 이름]_[클래스 이름]__[해시값] 형태로 자동으로 만들어줍니다.
6. styled-components를 사용하면 자바스크립트 파일 하나에 스타일까지 작성할 수 있기 때문에 .css 또는 .scss 확장자를 가진 스타일 파일을 따로 만들지 않아도 된다는 큰 이점이 있습니다.
7. styled-components와 일반 classNames를 사용하는 CSS/Sass를 비교했을 때, 가장 큰 장점은 props 값으로 전달해 주는 값을 쉽게 스타일에 적용할 수 있다는 것입니다.
Code Quiz
1. CSS Module을 사용한 클래스 이름을 두 개 이상 적용할 때 두번째 코드를 수정해주세요.
.wrapper {
background: black;
padding: 1rem;
color: white;
font-size: 2rem;
}
.inverted {
color: black;
background: white;
border: 1px solid black;
}
import React from 'react';
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={styles.wrapper}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
2. 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 {
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;
}
}
}
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;
}
1번 정답
import React from 'react';
import styles from './CSSModule.module.css';
const CSSModule = () => {
return (
<div className={`${styles.wrapper} ${styles.inverted}`}>
안녕하세요, 저는 <span className="something">CSS Module!</span>
</div>
);
};
export default CSSModule;
2번 정답
@import './styles/utils';
.SassComponent {
display: flex;
.box {
background: red; // 일반 CSS에서는 .SassComponent .box와 마찬가지
cursor: pointer;
transition: all 0.3s ease-in;
(...)
}
}
[리액트 스타터 3] 11장. 컴포넌트 성능 최적화 (0) | 2022.12.29 |
---|---|
[리액트 스타터3] 10장. 일정 관리 웹 애플리케이션 만들기 (0) | 2022.12.22 |
[리액트 스타터 3] 8장. Hooks (0) | 2022.11.24 |
[리액트 스타터 3] 7장. 컴포넌트의 라이프 사이클 메서드 (0) | 2022.11.17 |
[리액트 스타터 3] 6장. 컴포넌트 반복 (0) | 2022.11.10 |