다양한 방식의 리액트 컴포넌트 스타일링 방식 CSS, Sass, CSS Module, styled-components

velopert·2018년 10월 21일
42
post-thumbnail

리액트에서는 컴포넌트를 스타일링 할 때 다양한 방식을 사용 할 수 있습니다. 이 튜토리얼에서는 어떤 방식이 있는지, 자주 사용되는 것들을 하나하나 사용해보겠습니다.

저는 개인적으로 컴포넌트 하나마다 Sass 파일 하나씩 만들어서 관리를 하는것을 선호하고, 최근 만드는 프로젝트에서는 styled-components 를 사용하기도 합니다 :) CSS Module 은 작년에 자주 사용했었는데 사용하기가 요즘은 조금 불편하다고 느껴져서 잘 사용하고 있지 않고 있습니다.

이 포스트는 옛날에 제 블로그에 작성한 리액트 컴포넌트 스타일링 을 최신화한 포스트 입니다. CRA 버전이 달라져서 webpack 설정 부분에 변화가 있었습니다. 추가적으로, 이전에 집필한 리액트를 다루는 기술 에서 다룬 내용에서도 CRA 가 업데이트됨에 따라 설정방식이 달라졌는데 9장 컴포넌트 스타일링 대신 이 튜토리얼을 진행하시면 도움이 될 것입니다. 만약에 책이랑 똑같은 방식으로 진행을 하시려면 CRA 1.1.5 버전을 사용하셔야됩니다 (참고)


이 튜토리얼에선 create-react-app 을 사용하여 프로젝트를 직접 만들어서 진행하도록 하겠습니다.

가장 흔한 방식, 그냥 CSS

기존의 CSS 스타일링에 있어서 딱히 불편함을 느끼지 않고, 새로운 기술을 배우는게 불필요하다고 생각한다면 그냥 CSS 를 사용하시면 됩니다.

실제로, 그냥 작은 프로젝트라면 다른 시스템을 도입하는것 조차 고민하기도 귀찮습니다. 그런 상황에는 프로젝트에 기본적으로 적용되어있는 CSS 시스템을 사용하는것만으로도 충분합니다.

create-react-app 으로 만든 프로젝트를 보면, src 디렉토리에 App.js 와 App.css 가 있습니다.

App.js

App.css

우선, CSS 를 작성하게 될 때 가장 중요한점은 중복되는 클래스명 생성하지 않기 입니다. 클래스명이 중복되는것을 방지하기 위해선 꽤 다양한 방식이 있는데, 하나는 네이밍이고 하나는 CSS Selector 의 활용입니다.

create-react-app 으로 만든 App 컴포넌트를 보면, 클래스명이 컴포넌트명-클래스 으로 네이밍이 되어있습니다.

이런 형식 외에도, BEM Naming 이란 방식도 있는데, 컴포넌트마다 CSS 파일을 하나하나 만드는 상황에선 그렇게 권장되지는 않습니다. BEM 은 스타일을 하나의 파일에 몰아서 작성 할 때는 편리하지만, 클래스 파일이 여러개일때는 딱히 이렇게까지 이름을 지을 필요는 없다고 생각합니다.

다른 방식으로는, CSS Selector 를 활용하는 것 입니다. 예를들어서 위 CSS 를 다음과같이 변환을 하면:

App.css

App.js 에서 사용된 클래스명들은 다음과 같이 수정해주면 됩니다.

App.js

이런식으로, 컴포넌트의 최상위 html 요소에는 컴포넌트 이름과 동일한 이름으로 클래스명을 지으시고, 그 하단에서는 소문자로 입력하거나, 딱히 클래스명이 불필요한 경우엔 아예 생략을 할 수도 있죠.

Sass 사용하기

Sass (Syntactically Awesome Style Sheets: 문법적으로 짱 멋진 스타일시트) 는 CSS pre-processor 로서, 복잡한 작업을 쉽게 할 수 있게 해주고, 코드의 재활용성을 높여줄 뿐 만 아니라, 코드의 가독성을 높여주어 유지보수를 쉽게해줍니다.

이전 버전의 create-react-app 으로 만든 프로젝트에서는 Sass 를 사용하기위하여 별도의 작업을 추가적으로 해줬어야 하는데, v2 다음 버전부터는 그냥 바로 사용하실 수 있습니다.

Sass 가 익숙하지 않은 분들은 제가 쓴 포스트Sass 가이드 를 참고해보시는것을 권장드립니다.

Sass 에서는 두가지의 확장자 (.scss/.sass) 를 지원합니다. Sass 가 처음 나왔을떈 sass 만 지원되었고, sass 는 문법이 아주 다른데요:

.sass

.scss

더 많은 차이점들은 여기 서 비교해볼 수 있습니다. 보통 scss 문법이 더 많이 사용되므로, 우리는 .scss 확장자로 스타일을 작성하겠습니다.

한번 새 컴포넌트를 만들어서 Sass 를 사용해볼게요!

우선, node-sass 라는 라이브러리를 설치해야합니다. 이 라이브러리는 Sass 를 CSS 로 변환해줍니다.

다음, src 디렉토리에 SassComponent.scss 파일을 생성하세요.

SassComponent.scss

SassComponent.js 컴포넌트 파일도 만드세요.

SassComponent.js

그리고 해당 컴포넌트를 App.js 에서 렌더링하세요:

utils 함수 분리하기

자주 사용 될 수도 있는 Sass 변수 및 믹스인을 따로 파일로 분리해보겠습니다.

src 디렉토리 안에 styles 라는 디렉토리를 생성하고, 그 안에 utils.scss 파일을 만들어서, 기존 SassComponent 스타일 코드에 작성됐던 변수들과 믹스인을 잘라내서 이동시키겠습니다.

src/styles/utils.scss

이제, SassComponent.scss 에서 위 파일에서 선언한 것들을 사용해보겠습니다. 다른 scss 파일을 불러올 땐 @import 구문을 사용합니다.

src/SassComponent.scss

동일하게 작동하는지 확인해보세요!

sass-loader 설정 커스터마이징하기

이 부분은 Sass 를 사용 할 때 필수적인 작업은 아니지만, 해두면 좋을 수도 있는 작업입니다! 예를들어서, 방금 우리가 SassComponent 에서 styles/utils.scss 를 불러올 때, @import '../styles/utils.scss';

이런식으로 구현을 했었는데요, 만약에 프로젝트에 디렉토리를 깊숙한 구조로 만든다면, (예: src/components/somefeature/ThisComponent.js) import '../../../styles/utils.scss' 이런식으로 한참 거슬러올라가야 한다는 단점이 있습니다.

이 문제점를 핵려하는 방법은 Webpack에서 Sass 를 처리하는 sass-loader 의 설정을 커스터마이징하여 해결 할 수 있습니다. 그렇지만, create-react-app 으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위하여 세부 설정들이 모두 숨어있습니다. 이를 커스터마이징 하기 위해서는 yarn eject 명령어를 통하여 세부설정을 다시 밖으로 꺼내주어야 합니다.

create-react-app 으로 만든 프로젝트는 기본적으로 .git 설정도 되어있는데요, yarn eject 는 아직 git 에 커밋되지 않은 변화가 있다면 진행이 되지 않으니, 먼저 커밋을 해주어야 합니다.

VSCode 의 git UI 를 사용하셔서 현재까지 한 작업을 커밋하시거나, 다음 명령어를 통해서 커밋을 하세요.

그리고 나서, yarn eject 명령어를 실행합니다.

이제 config 경로가 프로젝트에 생겼을텐데요, 그 안에 webpack.config.js 를 열어보세요.

그리고, sassRegex 를 찾아보시면

이런 블록이 보일텐데, 여기서 use: 부분을 문자열을 다음과 같이 교체하세요:

getStylesLoaders 에서 두번째 파라미터로는 문자열형태로밖에 받아오지 못하니, 위와 같은 형태로 getStyleLoaders 로 만든 배열 뒷부분에다가 우리가 사용할 loader를 options 와 함께 적용을 해주었습니다.

작성 후, 서버를 껐다가 재시작하세요.

이제, utils.scss 파일을 불러올 때, 현재 수정하고 있는 scss 파일 경로가 어디에 위치하더라도, 앞부분에 상대경로를 입력 할 필요 없이 styles 디렉토리 기준 절대경로로 불러올 수 있습니다.

한번, SassComponent.scss 파일에서 기존 import 구문을 위와 같이 수정해보고 적용이 잘 되는지 확인해보세요.

이제 utils 를 사용 하는 컴포넌트가 있다면 위 한줄만 붙여넣어주시면 됩니다.

하지만! 이렇게 utils.scss 를 포함시키는것 조차 귀찮을 수도 있을 것 입니다. 그럴땐, sass-loader 의 data 설정을 사용하시면 됩니다. data 속성은, Sass 파일을 읽을 때 마다 앞부분에 특정 코드를 포함시켜줍니다.

webpack.config.js 를 열어서 data 를 포함시켜보세요:

이렇게 하고 나면, 모든 scss 파일에서 utils 를 사용 할 수 있게 되므로, SassComponent 에서 맨 위 import 구문을 지워도 정상적으로 작동 할 것입니다.

node_modules 에서 불러오기

yarn혹은 npm 을 통하여 설치한 Sass 라이브러리를 불러오는 방법을 알아봅시다. 가장 기본적인 방식으로는

과 같은 형식으로 위로 거슬러 올라가주어야 합니다. 하지만, 이것보다 더욱 쉬운 방법이 있는데요, 바로 물결 ~ 를 사용 하는 것 입니다.

제가 자주 사용하는 Sass 라이브러리중에서는 반응형 디자인을 쉽게 해주는 include-media 와 색상 팔레트인 open-color 가 있는데요, 만약 이를 사용하게 된다면, 다음과 같이 불러오면 됩니다.

패키지 디렉토리 안에 있는 scss 파일을 불러와야 하므로, node_modules 안으로 들어가서 디렉토리를 확인 후 필요한 파일을 불러오시면 됩니다.

CSS Module

CSS Module 은 CSS 클래스를 불러와서 사용 할 때 [파일이름]_[클래스이름]__[해쉬값] 형태로 클래스네임을 자동으로 고유한 값으로 만들어주어서 컴포넌트 스타일 중첩현상을 방지해주는 기술입니다. 이를 사용하기 위해선, [파일이름].module.css 이런식으로 파일을 저장하셔야 합니다.

한번, CSSModule.module.css 라는 스타일을 먼저 작성해봅시다.

src/CSSModule.module.css

자바스크립트로 컴포넌트도 작성해볼까요?

src/CSSModule.js

위 코드처럼 styles 를 불러오면 하나의 객체를 전달받게 되는데 그 안에는 CSS Module 에서 사용한 클래스 이름과, 해당 이름을 고유화시킨 값이 key-value 형태로 들어있습니다.

한번 console.log(styles) 를 하면 이런 결과가 나타납니다:

그리고, 이걸 사용하기 위해선 className={styles.[클래스이름]} 형태로 설정을 해주시면 됩니다.

다 작성하셨으면 App 컴포넌트에서 CSSModule 컴포넌트를 렌더링해봅시다.

src/App.js

이런식으로, 잘 작동하나요?

만약에 CSS Module 을 사용한 클래스이름을 두개 이상 적용 할 때는 이렇게 하시면 됩니다:

src/CSSModule.module.css

src/CSSModule.js

CSS Module 이 잘 작동하고 있나요?

classNames

classNames 는 CSS 클래스를 조건부로 설정 할 때 매우 유용한 라이브러리입니다. 그리고, CSS Module 을 사용 할 때 이 라이브러리를 함께 사용 한다면 여러 클래스를 적용할 때 편해집니다.

우선, classNames 를 설치하세요:

한번, classNames 의 기본적인 사용법을 훑어보겠습니다.

이런식으로 여러가지 종류의 파라미터를 조합해 CSS 클래스를 설정 할 수 있게 되기 때문에, 컴포넌트에서 조건부로 클래스를 설정할때 굉장히 편합니다. 예를 들어서 props 의 값에 따라 다른 스타일을 주게 하는게 쉬워지죠:

이렇게하면 위 엘리먼트의 클래스로는 highlighted 값이 true 이나 false 에 따라 highlighted 라는 클래스가 적용 될 것이고, 추가적으로 theme 으로 전달받는 문자열이 그대로 클래스에 적용 될 것입니다.

만약에 이런 라이브러리의 도움을 받지 않는다면 이런 형식으로 처리를 하셔야 될 겁니다:

classNames 를 쓰는 것이 훨씬 가독성이 높지요?

추가적으로, CSS Module 과 함께 쓸 땐 어떻게 편해질 수 있는지 알아보겠습니다.

classNames 를 불러올때 classnames/bind 를 사용하면 클래스를 넣어줄 때 마다 styles.[클래스] 형식으로 할 필요 없이, 사전에 미리 styles 에서 받아와서 사용하게끔 설정해두고 cx('class1', 'class2') 형태로 사용 할 수 있게 됩니다.

한번 CSSModule.js 컴포넌트를 다음과 같이 작성해보세요:

src/CSSModule.js

classnames/bind 를 사용하면, CSS Module 을 사용 할 때 클래스를 여러개 설정하거나 또는 조건부로 설정을 하게 될 때, 훨씬 편하게 작성 할 수 있겠죠?

Sass 와 함께 사용하기

Sass 를 사용할때도 파일이름 뒤에 .module.scss 을 입력해주면 CSS Module 로 사용 할 수 있습니다.
한번 파일 이름을 변경해보세요. 스타일 코드도 조금 바꾸겠습니다.

CSSModule.module.scss

그리고 CSSModule.js 에서도 상단에 .css 파일 대신 .scss 파일을 불러오세요.

이전과 똑같이 보여지고 있나요?

CSS Module 이 아닌 파일에서 CSS Module 사용하기

우리가 CSS Module 에서 글로벌 클래스를 정의 할 때 :global 을 썼었던 것 처럼, CSS Module 이 아닌 일반 .css/.scss 파일에서도 :local 을 통하여 CSS Module 을 사용 할 수도 있습니다.

styled-components

styled-components 는 현존하는 리액트 CSS-in-JS 관련 라이브러리 중에서 가장 잘나가는 라이브러리입니다. CSS-in-JS 는 이름이 그렇듯, 자바스크립트 파일 안에 CSS 를 작성하는 형태입니다. 해외의 큰 기업 - Atlassian, Reddit, coinbase 등에서도 사용되고 있고, 국내에서도 사용하는곳이 꽤 있습니다 - Channel.io, Huiseoul, Tumblebug 등..

styled-components 의 대체제는 현재 대표적으로 emotion 이 있습니다. 작동 방식은 꽤나 비슷합니다.

취향에 따라 갈릴수도 있지만, 분명히 알아두면 좋은 라이브러리인건 확실합니다.

한번 사용해볼까요?

이 라이브러리를 통해 한번 예제 컴포넌트를 만들어보겠습니다. 그냥 하나의 자바스크립트 파일안에 스타일까지 작성 할 수 있기 때문에 .css/.scss 파일 같은걸 만들 고민은 안하셔도 된다는게 큰 이점입니다.

한번 예제 컴포넌트 코드를 작성해보세요:

src/StyledComponent.js

Tagged Template Literal

styled-components 에서는 스타일을 입력 할 때 Tagged 템플릿 리터럴(Template Literal) 이라는 ES6 문법을 사용합니다. 이 문법을 사용하는 이유는, `` 를 사용할 때 내부에 JavaScript 객체나 함수가 전달 될 때 이를 따로 추출하기 위함입니다.

예를들어서:

위 코드는 [object Object] 이런식으로 문자열로 들어가게되면서 형태를 잃어버리게 되는데요, 만약에 함수를 다음과 같이 만들어서 사용하면 이 템플릿 리터럴 안에 넣어준
값들을 온전히 알아낼 수 있게 됩니다.

이렇게, 사이사이에 들어가는 JavaScript 값의 원본 값을 그대로 추출 할 수 있습니다.

스타일링 된 엘리먼트 만들기

스타일링 된 엘리먼트를 만들 땐, 상단에서 styled 를 불러오고 styled.태그명 을 사용하여 구현합니다:

저 자리에 button 이던, input 이던, 원하는걸 넣으시면 됩니다.

하지만, 만약에 보여줘야 할 태그 형식이 유동적이거나, 아니면 특정 컴포넌트에 스타일링을 해야 하는 상황이라면 다음과 같은 형태로 구현 할 수 있습니다:

스타일에서 props 조회하기

스타일링 한 컴포넌트에 전달하는 props 값을 스타일쪽에서 그대로 사용 하실 수 도 있습니다.

props 에 따른 조건부 스타일링

일반 CSS 클래스를 사용했더라면 주로 클래스이름으로 조건부 스타일링을 해왔었을텐데요, styled-components 에서는 그냥 props 로도 처리 가능합니다. 이렇게 말이죠:

한번 App에서 렌더링해볼까요?

src/App.js

반응형 디자인

styled-components 에서 반응형 디자인은 어떻게 하는지 알아봅시다. 일단, 일반 CSS 랑 똑같이 하면 되긴 하는데요:

src/StyledComponent.js 의 Box 컴포넌트

이런 작업을 함수화하여 훨씬 더 쉽게 할 수도 있답니다:

src/StyledComponent.js 상단부

어떤가요? 훨씬 간단해졌죠? 지금은 media 를 StyledComponent.js 에서 만들어주긴 했지만, 실제로 사용한다면 아예 다로 다른 파일로 분리시켜서 여기저기서 불러와서 사용하는게 훨씬 편할 것입니다.

기타

styled-components 는 정말 유용한 라이브러리이고 프로덕션 레벨에서도 사용가능한 충분히 성장된 라이브러리입니다.

글로벌 색상 관리를 도와줄 수 있는 Theme 이라는 기능도 있는데, 저는 딱히 엄청 선호하지는 않습니다. 단순히 자주사용되는 색상들을 담기 위한 것 이라면 그냥 객체형태로 저장을 해두고 불러와서 사용하는것이 훨씬 간단하며, 이 기능은 사용자가 서비스의 테마를 직접 변경 할 수도 있는 경우에 사용하면 조금 유용 할 수도 있습니다.

서버 사이드렌더링 도 지원이 되고있습니다.

정리

정말 다양한 리액트 컴포넌트 스타일링 방식을 배워보았습니다. 모두 쓸모있는 기술들이며, 이러한 방식들 중에서 뭘 사용 할 지 선택하는것은 여러분의 몫입니다.

profile
Frontend Engineer@RIDI. 개발을 재미있게 이것 저것 하는 개발자입니다.

24개의 댓글

comment-user-thumbnail
2018년 10월 24일

감사합니다! 드디어 utils를 간단하게 임포트 할 수 있게되었네요 :D

답글 달기
comment-user-thumbnail
2018년 10월 26일

항상 잘 보고 있습니다 :) 혹시 sass 부분 CRAv2에서 eject 없이 react-app-rewire만으로 해결하는방법이 있나요?

2개의 답글
comment-user-thumbnail
2018년 10월 31일

utils를 임포트 하려는데 css module도 함께 사용중이면 어떻게 해야 하나요???

utils 파일은 utils.module.scss인데.. 그대로 하면 되나용??

1개의 답글
comment-user-thumbnail
2018년 11월 2일

git add . git commit -m 'Commit before yarn eject' 을 해도 에러가 나는 경우는 어떻게 해결을 해야할까요..?

1개의 답글
comment-user-thumbnail
2018년 12월 1일

감사합니다. styled components 적응만 되면 좋아 보이네요

답글 달기
comment-user-thumbnail
2018년 12월 12일

좋은 정보 감사합니다.

답글 달기
comment-user-thumbnail
2019년 1월 3일

좋은 글 감사합니다!! 예전부터 궁금한건데 상수 cx 는 무슨 뜻인가요? 정말 궁금해서요;;

1개의 답글
comment-user-thumbnail
2019년 1월 12일

todo-list 도 바뀐 버전으로 한번 포스팅해주시지요.

답글 달기
comment-user-thumbnail
2019년 1월 22일

감사합니다.

답글 달기
comment-user-thumbnail
2019년 3월 7일

감사합니다. 책보다가 막혀서 일러주신 부분 보고 어느정도 해결은 한 것 같습니다만, 사소한 미해결 궁금증이 남아서 https://github.com/velopert/learning-react/issues/96 에 질문 드렸습니다.

답글 달기
comment-user-thumbnail
2019년 3월 31일

궁금한 점이 한가지 있습니다! 다름이 아니라 styled-component를 사용할 때 create-react-app v2등 css 까지 스플리팅을 하는 경우에는 코드스플리팅이 똑같이 이루어지나요?

답글 달기
comment-user-thumbnail
2019년 5월 16일

좋은 글 감사합니다. ^^
그런데, styled components에는 open-color같은 라이브러리가 없나요?

답글 달기
comment-user-thumbnail
2019년 9월 16일

혹시 styled-components 에서 기존 사스 함수를 쓰려면 어떻게 해야될까요?
단위 변환하는 식이 있어서
const StyledLogo = styled(ReactLogo) height: 25rem; width: 25rem; display: inline-block; margin: auto; .stroke { stroke : ${props => props.stroke} } ;

여기에서 기존엔 height 와 width를
@include function-name(width, 25px); 이런식으로 썼었어요.

답글 달기
comment-user-thumbnail
2019년 10월 14일

많은 도움 되고 있습니다.
한가지 궁금한게 있는데요,
상황에 따라서 쓰고 싶은데 scss와 module.scss를 같이 쓰고 싶은데
scss와 module.scss을 같이 사용하는건 불가능한가요?

웹팩에서 css-loader modules설정을 켜면 className="" 이 안먹고
설정을 끄면 className={}이 작동하지 않습니다.

한가지 더 module.scss로 저장을 안해도 .scss에서 모듈 작동이 되던데 제가 설정을 잘못한걸까요?

답글 달기
comment-user-thumbnail
2019년 12월 19일

안녕하세요. 정말 잘 보고 배우고 있습니다.
잘 보고 따라하다가 sass-loader 설정에서 오류가 생겨서 댓글로 남깁니다.

==변경전==
loader: require.resolve('sass-loader'),
options: {
includePaths: [paths.appSrc + '/styles'],
sourceMap: isEnvProduction && shouldUseSourceMap
}

==변경후==
loader: require.resolve('sass-loader'),
options: {
sassOptions: {
includePaths: [paths.appSrc + '/styles'],
sourceMap: isEnvProduction && shouldUseSourceMap
}
}

options object 에서 includePaths property를 인식하지 못하는 문제 였습니다.
변경 후는 sassOptions object로 넣어 주시면 됩니다.

2개의 답글
comment-user-thumbnail
2020년 1월 22일

'sass-loader 설정 커스터마이징 하기' 에서 data 프로퍼티를 추가하여 특정 코드를 포함시켜주는 부분에 수정이 필요합니다.
현재는 'data'가 아닌 'prependData' 로 입력을 해줘야지 정상 동작합니다.
코드로 보면
options: {
prependData: "@import 'utils';",
sassOptions: { ... },
}

로 입력을 해줘야 제대로 동작 합니다.

1개의 답글