리엑트 컴포넌트 스타일링하기
리액트에서 컴포넌트를 스타일링 하는 가장 기본적인 방법은 css 파일을 만들어서 컴포넌트에서 import 해서 사용하는 것 이다. 하지만 컴포넌트를 스타일링 할 때 다른 도구들을 사용하면 훨씬 더 편하게 작업을 할 수 있다.
- Sass
- CSS Module
- styled-components
이 블로그에서 위 3가지 기술들을 볼 것이다.
1. Sass
: CSS의 단점을 보완하기 위해 만든 CSS 전처리기이다. 보통 CSS를 사용하다보면 단순 반복되는 부분이 많은 등, 불편함이 느껴지기 마련인데, Sass는 이 부분을 거의 완전히 해소시켜주는 스타일시트 언어다. Sass에는 Sass와 SCSS가 있다.
또한 CSS의 확장팩 같은 언어이기 때문에 CSS 파일 그 자체를 SCSS로 확장자만 바꾸어주어도 정상적으로 작동한다.
다만 SASS 자체는 개발자용 언어이기 때문에 웹브라우저가 읽을 수 없다. 따라서 중간에 컴파일러를 거쳐서 CSS로 변환하여 HTML에 연결한다. - namuWiki
* SCSS( Sassy CSS)
말그대로 세시(Sassy)한 CSS라는 뜻이다. 실제 뜻을 생각해보면 CSS한 Sass가 더 맞는 것 같다. 가장 큰 특징은 기존 Sass의 문법에서, CSS의 원래 문법에서 사용되는 중괄호를 사용하여 CSS만 알던 사람들이 처음 접해도 직관적으로 의미를 이해할 수가 있다. 단순히 가독성만이 아니라 Sass의 기존문법이 들여쓰기 및 줄바꿈에 의존하는 문법임에 비해, SCSS는 중괄호가 있기에 공백에 의해 컴파일에러가 발생할 확률히 현저히 적다. 따라서 대부분의 사용자들이 SCSS 문법을 사용하여 Sass의 공식 문법으로 사용되고 있다. - namuWiki
+ 위 Sass를 설명할 때 SASS자체는 개발자용 언어이기 때문에 웹브라우저가 앍을 수 없다고 했다. 그래서 중간에 CSS로 변환해서 HTML에 연결해야 한다고 했다. 이를 위해 프로젝트 다이어리에 node-sass 라이브러리를 설치하여 사용한다.
이 sass에서는 파이썬의 클래스처럼 사용할 수 있는 크래스가 있다.
function Button({ children, size, color }) {
return (
<button className={classNames('Button', size, color)}>{children}</button>
);
}
이렇게 기본 함수를 만들고 사용할 인자(여기서는 'size'와 'color')를 classNames로 이렇게 적어둔 한수를 만든다.
그리고 scss파일에 .&라는 기호에 파라미터를 작성하고 형태는 함수모양으로 작성한다.
&.small {
height: 1.75rem;
font-size: 0.875rem;
padding-left: 1rem;
padding-right: 1rem;
}
// 색상 관리
&.blue {
background: $blue;
&:hover {
background: lighten($blue, 10%);
}
&:active {
background: darken($blue, 10%);
}
}
이렇게 적는다. ( '&.'는 각 유형별로 다 적고 함수 호출 할 때 유형별로 하나씩 쓴다.)
* ':&' 기호는 특정이벤트 발생시 발동이다.
추가로 이렇게 색상관련 코드들은 쓸데없이 반복되는 부분이 많다.
Sass 의 mixin 이라는 기능을 사용하여 쉽게 재사용 할 수 있다.
$blue: #228be6;
$gray: #495057;
$pink: #f06595;
@mixin button-color($color) {
background: $color;
&:hover {
background: lighten($color, 10%);
}
&:active {
background: darken($color, 10%);
}
}
.Button{
&.blue {
@include button-color($blue);
}
&.gray {
@include button-color($gray);
}
&.pink {
@include button-color($pink);
}
}
그리고
<Button size="large" color="gray">버튼</Button>
함수를 호출 할 때는 이렇게 하면 된다.
function Button({ props1, props2, { 이거요 }) {
return (
<button className={classNames('Button', props2, { 이거요 })}>
{props1}
</button>
);
}
그리고 인자값이 true일 때만 그 값을 받고싶으면 '이거요' 처럼 쓰면 된다.
function Button({ children, size, color, outline, fullWidth, onClick, onMouseMove }) {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
onClick={onClick}
onMouseMove={onMouseMove}
>
{children}
</button>
);
}
만약 함수를 다 작성하고 온클릭이나 온마우스를 넣고싶다면 이렇게 넣어야한다.
그러나 여기서는 spread 와 rest를 사용할 수 있다.
function Button({ children, size, color, outline, fullWidth, ...rest }) {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
{...rest}
>
{children}
</button>
);
}
이거를 이렇게 써주면 된다.
2. CSS Module
: 리액트 프로젝트에서 컴포넌트를 스타일링 할 때 CSS Module 이라는 기술을 사용하면, CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있다. CSS 파일의 확장자를 .module.css 로 하면 된다.
import React from "react";
import styles from "./Box.module.css";
function Box() {
return <div className={styles.Box}>{styles.Box}</div>;
}
export default Box;
이렇게 그냥 css파일을 받아와서 쓸 때 클래스 이름을 파일 경로, 파일 이름, 클래스 이름, 해쉬값 등을 사용해서 고유하게 만든다.
위 방법을 이럴 때 좋다.
- 레거시 프로젝트에 리액트를 도입할 때 (기존 프로젝트에 있던 CSS 클래스와 이름이 중복되어도 스타일이 꼬이지 않게 해줍니다.)
- CSS 클래스를 중복되지 않게 작성하기 위하여 CSS 클래스 네이밍 규칙을 만들기 귀찮을 때
조건 삼항 연산자
: 조건 (삼항) 연산자는 JavaScript에서 세 개의 피연산자를 받는 유일한 연산자이다. 앞에서부터 조건문, 물음표(?), 조건문이 참(truthy)일 경우 실행할 표현식, 콜론(:), 조건문이 거짓(falsy)일 경우 실행할 표현식이 배치된다.
condition ? exprIfTrue : exprIfFalse;
react-icons 라이브러리
: 다양한 아이콘들을 React 프로젝트에서 사용할 수 있게 해주는 라이브러리이다.
> npm install react-icons --save
> yarn add react-icons --save
React icons 홈페이지에 가서 원하는 아이콘을 고르고
import { 아이콘명 } from 'react-icons/아이콘명 앞글자';
이렇게 쓰면 된다.
여기서 주의할 점은 'react-icons/아이콘명 앞글자' 이 부분이다.
아이콘의 이름들은 모두 파스칼 케이스로 되어 있다. (파스칼 케이스 : 첫 글자와 중간 글자가 모두 대문자)
여기서 파스칼 케이스 중 첫 번째 부분을 'react-icons/' 뒤에 소문자로 써야 한다.
'FiMenu'를 예로 들면, 'FiMenu'의 첫 번째 부분인 'Fi'를 소문자 'fi'로 바꿔 'react-icons/fi' 이렇게 써야 정상적으로 작동이 된다.
import { FiMenu } from 'react-icons/fi';
import { FaApple } from 'react-icons/fa';
이렇게
CSS Module 사용
- 먼저 [컴포넌트 이름].module.css 라는 이름의 css 를 만들어 준다.
- 컴포넌트 상단에 해당 css를 import (styles와 같은 변수에 담을 수 있음)
- styles 객체 안에 있는 클래스 값들을 styles.Box와 같은 이름으로 가져오게 되면 해싱 된 값으로 보여지게 됨
- 만약에 클래스 이름에 -가 들어가 있으면 styles['my-class'] 와 같이 가져옴
여러 개가 있다면 ${styles.one} ${styles.two}와 같이 조회해야함.
-> 이런 번거로움을 개선한 것이 classnames 라이브러리의 bind기능을 이용해 조건부 렌더링이나 여러 개의 클래스를 사용시 좀 더 편하게 사용할 수 있게 함.
<사용볍>
- npm install classnames
- component 파일 상단에 import classNames from 'classnames/bind';
- const cx = classNames.bind(styles);
- className={cx("클래스 이름")} 과 같이 해당 스타일 가져옴
- 여러 클래스 사용
cx('one', 'two')
- 조건부 스타일링
cx("my-component", {
condition: true,
});
cx("my-component", ["another", "classnames"]);
3. styled-components
: styled-components 는 현존하는 CSS in JS 관련 리액트 라이브러리 중에서 가장 인기 있는 라이브러리이다.
: CSS in JS는 이름에서 알 수 있듯이 CSS-in-JS를 사용하면 자바스크립트 또는 타입스크립트 코드에서 직접 CSS를 작성하여 리액트 컴포넌트 스타일을 지정할 수 있습니다.
먼저 Tagged Template Literals를 배워보자
Template Literals
: 동적인 문자열을 처리할 때 우리가 많이 사용했던 것이다.
const userName = 'jongyun';
const age = 20;
const output = `Hi, ${userName} and I am ${age}.`;
..... 이렇게
Tagged Template Literals
: 그렇다면 Tagged Template Literals는 무엇이냐, ' ${ } ' 안에 일반 문자열 / 숫자가 아닌 객체를 넣으면 타입에 상관없이 Function, Number, Array, Object 등을 전달하고 이를 실행할 수 있게 되는 것이다.
function transform(staticData, ...dynamicData) {
console.log(staticData); // ["Hi, ", " and I am ", "."]
console.log(dynamicData); // ["JongYun", 20]
}
transform`Hi, ${userName} and I am ${age}.`;
그냥 function으로 실행하면 20이 string로 나왔을 텐데 이렇게 Tagged Template Literals를 사용하면 Number로 나온다.
우선 styled-components를 설치한다.
$ npx create-react-app styling-with-styled-components
$ cd styling-with-styled-components
$ yarn add styled-components
..... 이렇게
styled-components 사용
- HTML 엘리먼트를 스타일링 할 때는 모든 알려진 HTML 태그에 대해서 이미 속성이 정의되어 있기 때문에 해당 태그명의 속성에 접근한다.
import styled from "styled-components";
styled.button`
// <button> HTML 엘리먼트에 대한 스타일 정의
`;
- React 컴포넌트를 스타일링 할 때는 해당 컴포넌트를 임포트 후 인자로 해당 컴포넌트를 넘기면 된다.
import styled from "styled-components";
import Button from "./Button";
styled(Button)`
// <Button> React 컴포넌트에 스타일 정의
`;
두가지 문법 모두 ES6의 Tagged Template Literals을 사용해서 스타일을 정의한다.
그리고 styled 함수는 결국 해당 스타일이 적용된 HTML 엘리먼트나 React 컴포넌트를 리턴한다.
polished
: Sass 를 사용 할 때에는 lighten() 또는 darken() 과 같은 유틸 함수를 사용하여 색상에 변화를 줄 수 있었는데, CSS in JS 에서도 비슷한 유틸 함수를 사용하기 위한 라이브러리이다.
우선 이 패키지를 받는다.
$ yarn add polished
import {darken, lighten} from 'polished';
그리고 얘를 받고
&:hover{
background: ${lighten(0.1, '#228be6')};
}
&:active{
background: ${darken(0.1, '#228be6')};
}
이 lighten과 darken은 뒤의 색을 x%(여기서는 10%) 더 밝게, 어둡게 만들어주는 것이다.
ThemeProvider
: ThemeProvider는 Styled-Components안에 들어있는 컴포넌트중 하나이다. ThemeProvider를 사용하면 하위에 있는 태그들에게 모두 스타일이 미치게되어 전역적으로 스타일링을 해줄 수 있다.
import styled, { ThemeProvider } from 'styled-components';
우선 ThemeProvider를 받는다.
그리고 ThemeProvider를 사용하려면 다음과 같이 사용할 컴포넌트 계층 구조 내에 포함시키면 된다.
const theme = {
colors: {
primary: '#fca311',
secondary: '#ffba08',
},
};
function App() {
return (
<ThemeProvider theme={theme}>
{/* 테마를 사용할 컴포넌트 계층 구조 */}
</ThemeProvider>
);
}
이렇게 theme에 props들을 넣고 theme 하위에서 사용해주면 된다.
function App() {
return (
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
</ThemeProvider>
);
}
이렇게 작성한다고 가정하자
theme provider를 Button 컴포넌트에서 사용할 수 있다.
import React from 'react';
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const StyledButton = styled.button`
/* 공통 스타일 */
/* 크기 */
/* 색상 */
${props => {
const selected = props.theme.palette[props.color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
function Button({ children, ...rest }) {
return <StyledButton {...rest}>{children}</StyledButton>;
}
Button.defaultProps = {
color: 'blue'
};
export default Button;
버튼 컴포넌트에서 이렇게 사용할 수 있다.
여기에 사이즈 관련 코드도 넣을 수 있다.
const sizes = {
large: {
height: '3rem',
fontSize: '1.25rem'
},
medium: {
height: '2.25rem',
fontSize: '1rem'
},
small: {
height: '1.75rem',
fontSize: '0.875rem'
}
};
const sizeStyles = css`
${({ size }) => css`
height: ${sizes[size].height};
font-size: ${sizes[size].fontSize};
`}
`;
..... 이렇게
이전에 배웠던 것들을 잘 활용하면 outline하고 fullwidth 버튼도 만들 수 있을 것이다.
Dialog
import React from 'react';
import styled from 'styled-components';
import Button from './Button';
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
`;
const DialogBlock = styled.div`
width: 320px;
padding: 1.5rem;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5rem;
}
p {
font-size: 1.125rem;
}
`;
const ButtonGroup = styled.div`
margin-top: 3rem;
display: flex;
justify-content: flex-end;
`;
const ShortMarginButton = styled(Button)`
& + & {
margin-left: 0.5rem;
}
`;
function Dialog({
title,
children,
confirmText,
cancelText,
onConfirm,
onCancel,
visible
}) {
if (!visible) return null;
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<ShortMarginButton color="gray" onClick={onCancel}>
{cancelText}
</ShortMarginButton>
<ShortMarginButton color="pink" onClick={onConfirm}>
{confirmText}
</ShortMarginButton>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
이렇게 dialog를 만들 수 있다.
위처럼 dialog의 기능을 구현하는 것은 간단하다.
이제 트랜지션을 구현해보자
트랜지션
: 트랜지션(transion)이란 한 장면에서 다른 장면으로 바뀔 때, 또는 다른 기능의 실행 시 사용하는 장면 전환기술을 의미한다. 트랜지션 효과를 적용 할 때에는 CSS Keyframe 을 사용하며, styled-components 에서 이를 사용 할 때는 keyframes 라는 유틸을 사용한다.
- Dialog가 나타날 때 DarkBackground 쪽에는 서서히 나타나는 fadeIn 효과를 주고, DialogBlock 에는 아래에서부터 위로 올라오는 효과를 보여주는 slideUp 효과를 줘보겠다. 애니메이션의 이름은 여러분들이 마음대로 지정 할 수 있다.
fadeIn 애니메이션
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
- fadeIn은 요소의 불투명도를 점진적으로 0에서 1로 변경하여 요소가 서서히 나타나는 효과를 준다.
- from 단계에서는 opacity가 0으로 설정되어 요소가 보이지 않으며, to 단계에서는 opacity가 1로 설정되어 요소가 완전히 보이게 된다.
slideUp 애니메이션
const slideUp = keyframes`
from {
transform: translateY(200px);
}
to {
transform: translateY(0px);
}
`;
- slideUp은 요소를 아래에서 위로 슬라이드하여 나타나게 하는 효과를 준다.
- from 단계에서는 transform: translateY(200px);로 설정되어 요소가 아래로 200px 이동된 상태이다.
- to 단계에서는 transform: translateY(0px);로 설정되어 요소가 원래 위치로 돌아오며 나타난다.
- 사라지는 효과를 구현하려면 Dialog 컴포넌트에서 두개의 로컬 상태를 관리해주어야 한다. 하나는 현재 트랜지션 효과를 보여주고 있는 중이라는 상태를 의미하는 animate, 나머지 하나는 실제로 컴포넌트가 사라지는 시점을 지연시키기 위한 localVisible 값이다.
- 그리고 useEffect 를 하나 작성해야 하는데, visible 값이 true 에서 false 로 바뀌는 시점을 감지하여 animate 값을 true 로 바꿔주고 setTimeout 함수를 사용하여 250ms 이후 false로 바꿔야 한다.
추가적으로, !visible 조건에서 null 를 반환하는 대신에 !animate && !localVisible 조건에서 null 을 반환하도록 수정해야 한다.
fadeOut 애니메이션
const fadeOut = keyframes`
from {
opacity: 1;
}
to {
opacity: 0;
}
`;
- fadeOut은 요소의 불투명도를 점진적으로 1에서 0으로 변경하여 요소가 서서히 사라지는 효과를 준다.
slideDown 애니메이션
const slideDown = keyframes`
from {
transform: translateY(0px);
}
to {
transform: translateY(200px);
}
`;
- slideDown은 요소를 위에서 아래로 슬라이드하여 사라지게 하는 효과를 준다.
1. 상태 관리
const [animate, setAnimate] = useState(false);
const [localVisible, setLocalVisible] = useState(visible);
- animate: 트랜지션 효과를 보여주는 상태를 관리한다.
- localVisible: 컴포넌트가 실제로 사라지는 시점을 지연시키기 위한 상태를 관리한다.
2. useEffect 사용
useEffect(() => {
if (localVisible && !visible) {
setAnimate(true);
setTimeout(() => setAnimate(false), 250);
}
setLocalVisible(visible);
}, [localVisible, visible]);
- visible 값이 true에서 false로 바뀌는 것을 감지하여 animate 값을 true로 설정하고, 250ms 후에 false로 바꾼다.
- setLocalVisible(visible): localVisible 값을 visible 값으로 업데이트한다.
3. 조건 수정
if (!animate && !localVisible) return null;
- !visible 조건 대신 !animate && !localVisible 조건에서 null을 반환하여 애니메이션이 끝날 때까지 컴포넌트가 화면에 남아 있도록 한다.
이렇게 코드를 잘~~ 활용해 트랜지션을 작성하면 된다 :)
'WINK-(Web & App) > React.js 스터디' 카테고리의 다른 글
[2024 여름방학 React.js 스터디] 김태일 #5주차 "리액트 컴포넌트 스타일링" (0) | 2024.08.09 |
---|---|
[2024 여름방학 React.js 스터디] 김민서 #5주차 (0) | 2024.08.09 |
[2024 여름방학 React.js 스터디] 이정욱 #4주차 react 하기 힘들어요 (0) | 2024.08.02 |
[2024 여름방학 React.js 스터디] 이가인 #4주차 (0) | 2024.08.02 |
[2024 여름방학 React.js 스터디] 이종윤 #4주차 (0) | 2024.08.02 |