CSS를 작성할 때 가장 중요한 점은 CSS 클래스를 중복되지 않게 만드는 것이다
CSS 클래스 중복 방지를 위한 방법 중 하나는 이름을 지을 때 특별한 규칙을 사용하는 것이고, 또 다른 하나는 CSS Selector를 활용하는 것이다
.App-logo {...}
.App-header {...}
.App-link {...}
리액트 프로젝트를 시작해봤다면 위와 같이 자동 생성된 App.css 파일을 본 적 있을 것이다
여기서는 컴포넌트 이름-클래스
형태로 클래스 이름을 지은 것을 확인할 수 있다
이와 비슷한 CSS 방법론 중 하나로 BEM 네이밍 방식이 있다
BEM 네이밍 방식이란 이름을 지을 때 일종의 규칙을 준수하여 해당 클래스가 어디에서 어떤 용도로 사용되는지 명확하게 작성하는 방식이다
ex) .card__title-primary
CSS Selector를 사용하면 CSS 클래스가 특정 클래스 내부에 있는 경우에만 스타일을 적용할 수 있다
.App .logo {...}
컴포넌트의 최상위 html 요소에는 컴포넌트의 이름으로 클래스 이름을 짓고, 그 내부에서는 소문자를 입력하거나 header 같은 태그를 사용하여 클래스 이름이 불필요한 경우 아예 생략할 수 있다
Sass는 CSS 전처리기로 복잡한 작업을 쉽게 할 수 있도록 해주고, 스타일 코드의 재활용성을 높이며 코드의 가독성을 높여 유지 보수를 더욱 쉽게 해준다
Sass에서는 .scss와 .sass 두 가지 확장자를 지원한다
$font-stack: Helvetica, sans-serif
$primary-color: #333
body
font: 100% $font-stack
color: $primary-color
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-color;
}
위 코드를 보면 두 개의 문법은 차이가 있는 것을 알 수 있다
.sass 확장자는 중괄호와 세미콜론을 사용하지 않으며 .scss 확장자는 기존 CSS를 작성하는 방식과 비교해서 문법이 크게 다르지 않다
보통 .scss 확장자를 더 많이 사용한다
// 변수 사용
$red: #fa5252;
$orange: #fd7e14;
// 믹스인 만들기(재사용되는 스타일 블록을 함수처럼 사용 가능)
@mixin square($size) {
$calculated: 32px * $size;
width: $calculated;
height: $calculated;
}
.SassComponent {
.box { // .SassComponent .box와 동일
background: red;
cursor: pointer;
&.red {
// .red 클래스가 .box와 함께 사용되었을 때
background: $red;
@include square(1);
}
&.orange {
background: $orange;
@include square(2);
}
}
}
여러 파일에서 사용될 수 있는 변수 및 믹스인은 다른 파일로 따로 분리해 작성한 뒤 필요한 곳에서 쉽게 불러와 사용할 수 있다
보통 파일명은 utils.scss 로 한다
다른 scss 파일을 불러올 때는 최상단에 @import
구문을 사용한다
CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 고유한 값인 [파일 이름]_[클래스 이름]__[해시값]
형태로 자동으로 만들어 컴포넌트 스타일 클래스 이름이 중첩되는 현상을 방지해 주는 기술이다
자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 사용 가능
:global .something {...}
특정 클래스가 웹 페이지에서 전역적으로 사용되는 경우라면 :global
을 앞에 입력해줘야 한다
반대로 CSS Module이 아닌 일반 .css/.scss 파일에서
:local
을 사용해 CSS Module을 사용할 수 있다
import styles from './css파일';
(...)
<span className = {styles.wrapper}></span>
<span className = "something"></span>
클래스를 적용하고 싶은 JSX 엘리먼트에 className = {styles.[클래스 이름]}
형태로 전달해주면 된다
:global을 사용해 전역적으로 선언한 클래스의 경우 문자열로 넣어 준다
<div className = {`${styles.wrapper} ${styles.inverted}`}></div>
또는
className = {[styles.wrapper, styles.inverted].join(' ')}
classnames는 CSS 클래스를 조건부로 설정할 때 매우 유용한 라이브러리로, CSS Module을 사용할 때 여러 클래스를 적용함에 있어 매우 편리하다
import classNames from 'classnames';
classNames('one', 'two'); // 'one two'
classNames('one', { two: true }); // 'one two'
classNames('one', { two: false }); // 'one'
classNames('one', ['two', 'three']); // 'one two three'
const myClass = 'hello';
classNames('one', myClass, { myCondition: true }); // 'one hello myCondition'
이와 같이 여러 종류의 파라미터를 조합해 CSS 클래스를 설정할 수 있기 때문에 컴포넌트에서 조건부로 클래스를 설정할 때 매우 편하다는 장점이 있다
classnames의 bind 함수를 사용하면 클래스를 넣을 때마다 styles.[클래스 이름]
형태를 사용할 필요가 없다
import classNames from 'classnames/bind';
import styles from './파일 이름';
const cx = classNames.bind(styles); // 미리 styles에서 클래스를 받아오도록 설정
(...)
<div className = {cx('wrapper', 'inverted')}></div>
Sass를 사용할 때도 확장자명을 .module.scss
라고 하면 CSS Module로 사용할 수 있다
이 방식은 자바스크립트 파일 안에 스타일을 선언하는 방식으로 CSS-in-JS
라고 부른다
관련 라이브러리가 많아서 참고해도 좋다
styled-components는 CSS-in-JS 라이브러리 중에서 개발자들이 가장 선호하며, 이와 비슷하고 대체할 수 있는 라이브러리로 emotion이 대표적이다
import styled, { css } from "styled-components";
const Box = styled.div`
background: ${(props) =>
props.color || "blue"}; // props로 넣어준 값 직접 전달 가능
padding: 1rem;
display: flex;
`;
const Button = styled.button`
background: white;
// & 문자를 사용해서 Sass처럼 자신 선택 가능
&:hover {
background: blue;
}
// inverted 값이 true일 때 특정 스타일 부여
${(props) =>
props.inverted &&
css`
background: none;
$:hover {
background: white;
}
`};
`;
const StyledComponent = () => (
<Box color="black">
<Button inverted={true}>테두리만</Button>
</Box>
);
export default StyledComponent;
VS Code에서 styled-components를 사용할 때 코드 신택스 하이라이팅이 제대로 적용되지 않는 문제가 발생한다 👉 VS Code 마켓플레이스에서
vscode-styled-components
설치
styled-components에서 스타일을 작성할 때 `
을 사용해 만든 문자열에 스타일 정보를 넣어 줬는데, 이 문법을 Tagged 템플릿 리터럴이라고 한다
Tagged 템플릿 리터럴은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다
`hello ${{foo: 'bar'}} ${() => 'world'}!` // "hello [object Object] () => 'world'!"
일반 템플릿의 경우 템플릿에 객체나 함수를 넣으면 형태를 잃게 된다
function tagged(...args) {
console.log(args);
}
tagged`hello ${{foo: 'bar'}} ${() => 'world'}!`
하지만 위와 같이 tagged
라는 함수 뒤에 템플릿 리터럴을 넣어 주면 템플릿 안에 넣은 값을 온전히 추출할 수 있다
styled-components는 이러한 속성을 사용해 styled-components로 만든 컴포넌트의 props를 스타일 쪽에서 쉽게 조회할 수 있도록 해 준다
styled-componets를 사용해 스타일링된 엘리먼트를 만들 때는 컴포넌트 파일의 상단에서 styled
를 불러오고 styled.태그명
을 사용해 구현한다
import styled from 'styled-components';
const MyComponent = styled.div`(...)`;
만약 사용해야 할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링해 주고 싶다면 다음과 같은 형태로 구현할 수 있다
const MyInput = styled('태그타입')`(...)` // 태그의 타입을 styled 함수의 인자로 전달
const StyledLink = styled(Link)`(...)` // 컴포넌트 형식의 값을 넣어줌
컴포넌트를 styled의 파라미터에 넣는 경우 해당 컴포넌트에 className props를 최상위 DOM의 className 값으로 설정하는 작업이 내부적으로 되어 있어야 함
const Sample = ({ className }) => { return <div className={className}>Sample</div>; }; const StyledSample = styled(Sample)`(...)`;
className을 사용해 조건부 스타일링을 했던 일반 CSS 클래스와 달리 styled-components에서는 props로 처리할 수 있다
import styled, { css } from "styled-components";
const Button = styled.button`
// inverted 값이 true일 때 특정 스타일 부여
${(props) =>
props.inverted &&
css`
background: none;
$:hover {
background: white;
}
`};
`;
const StyledComponent = () => (
<Button inverted={true}>테두리만</Button>
);
이처럼 스타일 코드 여러 줄을 props에 따라 넣어 줘야 할 때는 css를 import 해야 한다
브라우저의 가로 크기에 따라 다른 스타일을 적용하기 위해서는 일반 CSS를 사용할 때와 똑같이 media query
를 사용하면 된다
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`
(...)
${media.desktop`width: 768px;`}
${media.tablet`width: 100%;`};
`;
이처럼 media를 한번 선언하면 media를 사용할 때 스타일 쪽의 코드가 간단해진다
자세한 건 여기를 참고하면 된다
리액트에서 컴포넌트를 스타일링 하기 위한 다양한 방식을 알게 되었다
나는 그동안 CSS Module
을 사용해왔었는데, Sass도 한 번 사용해봐야겠다고 생각했다