본 포스팅에는
관련 내용이 기록되어 있습니다.
일반 CSS파일을 활용해 컴포넌트를 스타일링하는 과정에서 이상한 점을 발견했습니다.
아래 코드처럼 PageLayout
컴포넌트에만 가운데 정렬이 반영이 되고, 다른 컴포넌트들에는 영향을 주지 않을 것이라 생각했습니다.
하지만 main
태그가 있는 다른 모든 페이지(PageLayout컴포넌트를 사용하지도 않은)에 해당 스타일이 전역으로 반영되는 의도치 않은 결과가 발생했습니다.
//PageLayout 컴포넌트에 사용될 pagelayout.css
main {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
//PageLayout 컴포넌트
import './pagelayout.css';
export default function PageLayout({ children }) {
return <main className="page-layout">{children}</main>;
}
어째서 이런 현상이 발생했을까요?
저는 main
선택자가 PageLayout 컴포넌트에만 적용되는 것을 예상했지만, CSS는 전역
으로 적용이 됩니다.
클래스명과 id선택자를 사용하면 마치 지역적으로 적용되는 것처럼
보이지만, 동일한 클래스명, id가 존재하는 경우에는 스타일이 덮어씌워집니다.
서로 다른 css파일이지만, 동일한 클래스명을 가지고 있는 예시를 만들어보면서 본 이슈에 대한 이해를 높여보도록 하지요.
Home.tsx
import { Link } from 'react-router-dom';
import './home.css';
export default function Home() {
return (
<main className="main">
<section>This is Home Page</section>
<div>
<Link to="/about">About Page</Link>
</div>
<div>
<Link to="/sign-up">Sign Up</Link>
</div>
<div>
<Link to="/sign-in">Sign In</Link>
</div>
</main>
);
}
home.css
.main {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
height: 100%;
background-color: red;
}
About.tsx
import './about.css';
export default function About() {
return (
<main className="main">
<section>This is About Page</section>
</main>
);
}
about.css
.main {
display: flex;
justify-content: flex-start;
align-items: flex-start;
height: 100%;
}
home.css와 about.css는 둘 다 .main
클래스 선택자에 서로 다른 스타일을 주고 있습니다. 그러면 결과는 어떨까요?
Home 페이지
About 페이지
두 css파일에서 적용한 속성들이 섞여있습니다.
Home에서는 분명 justify-content: flex-end;
를 설정했지만, about.css파일이 엎어씌워졌기에 justify-content: flex-start;
가 적용된 모습입니다.
About페이지에는 적용하지 않은 배경색이 red 색상으로 적용되어있네요.
지금은 파일의 개수가 적어서 문제가 별로 되어보이지 않지만, 프로젝트 규모가 커지고 나서 이런 스타일 충돌을 발견하게 되면 문제가 되는 파일을 추적해나가는 것이 굉장히 어려울 것입니다.
또한 여러 명이 협업하는 경우, 중복되는 클래스명이 생기지 않으리란 보장도 없습니다.
스타일을 지역적
으로 적용하는 방법이 적절한 해결책이 되겠습니다. 그 방법에는 크게 세 가지 정도 있을 듯 합니다.
클래스 네이밍을 겹치지 않게 사용하기
=> 하지만 사용하는 클래스가 많아지면 일일이 클래스 네이밍이 겹치는지 여부를 확인해야하니 번거로울 듯 합니다.
CSS Module 사용하기
locally-scoped
된다고 합니다.CSS 파일이름을 pagelayout.module.css
로 변경해주고,
main태그 선택자를 클래스 선택자로 바꿔줍니다.
.main {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
컴포넌트에 아래와같이 클래스를 적용해 줍니다.
import styles from './pagelayout.module.css';
export default function PageLayout({ children }) {
return <main className={styles.main}>{children}</main>;
}
그 과정에서 아래와같은 에러를 만나실 수도 있는데요, 조사결과 그 원인은
Cannot find module '~.module.css' or its corresponding type declarations.
(1) Webpack에서 css-loader
가 설정이 안 되어 있을때
(2) typescript와 함께 사용하는 경우에는 css-module에 대한 타입선언을 찾을 수 없을때였습니다.
저같은 경우는 웹팩에 이미 cssloader 관련 설정이 되어 있기에, (2)케이스라 생각하고
yarn add --dev @types/css-modules
를 통해 관련 타입선언을 설치해주었더니 문제가 해결되었습니다.
CSS 모듈은 일반 CSS와 비교했을 때는 지역적으로 스타일을 적용할 수 있다는 점에서 장점이 있습니다.
하지만 가독성 있는 클래스명을 작성하지 않는 경우에는 스타일 파일을 확인하면서 스타일을 일일이 체크해보아야하기에 비효율적이라는 생각이 들었습니다.
프론트엔드개발자 입장에서는 컴포넌트 파일에서 요소의 스타일까지 한 번에 작성하고 확인할 수 있으면 좋을텐데요.
CSS-in-JS 방식의 스타일링을 지원하는 라이브러리들의 등장은 위와같은 요구를 참 아름답게 충족시켜주었다고 생각합니다. CSS-in-JS의 가장 큰 특징은 css가 빌드 타임에 컴파일되는 것이 아니라 런타임에 생성된다는 점입니다. 이렇게 되면 기존 CSS 방식에 비해 브라우저에 부하가 더 가해지겠네요.
대표적인 라이브러리는 emotion
, styled-components
가 존재합니다. 예전에는 emotion
이 번들 사이즈가 작아서 채택하는 경우가 많았는데, 요즘은 또 그렇지만은 않은 것 같습니다. 문법도 거의 동일하니 취향에 따라 취사선택하시지요. 저는 이번에는 emotion을 설치해보겠습니다.
https://emotion.sh/docs/introduction
yarn add @emotion/styled @emotion/react
Emotion includes TypeScript definitions for @emotion/react and @emotion/styled. These definitions infer types for css properties with the object syntax, HTML/SVG tag names, and prop types.
이미 @emotion/styled @emotion/react에는 타입선언이 포함되어있기에, 별도로 타입 패키지를 설치할 필요는 없습니다.
https://emotion.sh/docs/typescript
그리고 설치 뒤에는
//tsconfig.json
"jsxImportSource": "@emotion/react",
tsconfig.json
에서 이 설정을 추가해줍시다! 이 설정을 하게 되면,
CSS 템플릿 리터럴, CSS객체를 컴포넌트의 css prop으로 전달하여 스타일링을 정의할 수 있습니다.
import { css } from '@emotion/react';
export default function PageLayout({ children }) {
return (
<Main>
{children}
<div
css={css`
background-color: red;
`}
>
CSS props를 사용가능하게 만드는 옵션
</div>
</Main>
);
}
@emotion/babel-plugin is highly recommended, but not required in version 8 and above of Emotion.
매우 추천되니 바벨 설정도 함께 진행을 해줍시다!
https://emotion.sh/docs/@emotion/eslint-plugin
자 이제 Emotion으로 자유롭게 스타일링을 진행할 수 있습니다!
import { css } from '@emotion/react';
import styled from '@emotion/styled';
export default function PageLayout({ children }) {
return (
<Main>
{children}
<div
css={css`
background-color: red;
`}
>
CSS props를 사용가능하게 만드는 옵션
</div>
<div css={objectStyle}></div>
</Main>
);
}
const objectStyle = css({
width: 300,
height: 200,
backgroundColor: 'blue',
});
const Main = styled.main`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
`;
개발자에게 '불편함'이란 생산성을 저해하는 모든 요소들입니다. 그리고 개발자들은 각종 불편함을 해소하기 위해 여러가지 기술적인 해결책들을 제시합니다. 그 중 오늘 알아본 CSS관련 패키지들은 스타일링의 불편함
을 해소하기 위한 기술들이었습니다.
styled-components
, emotion
, tailwind css
등의 패키지들의 사용법을 잘 익혀놓는 것 뿐 아니라, 기술을 사용하면서 왜 이 기술을 사용할까?
를 한 번쯤 깊이 느끼는 과정도 중요하다는 생각이 들었습니다.
Stack Overflow:
https://stackoverflow.com/questions/47090574/how-to-make-react-css-import-component-scoped
https://www.freecodecamp.org/news/the-tradeoffs-of-css-in-js-bee5cf926fdb/