1. Sass
Sass는 CSS pre-processor로서 복잡한 작업을 쉽게 해주고, 코드의 재활용성과 가독성을 높여주어 유지보수를 쉽게 해준다.
Sass에서는 .scss, .sass 두가지 확장자를 지원한다.
보통 .scss 문법이 더 많이 사용되고, 이 글에서는 .scss를 사용할 것이다.
1) 시작
프로젝트 디렉터리에 다음 명령어를 사용하여 node-sass 라이브러리(Sass를 CSS로 변환해주는 라이브러리)를 설치한다.
$ cd styling-with-sass
$ yarn add node-sass
// 또는
$ npm install node-sass
2) Button 컴포넌트 만들기
Button 컴포넌트를 만들고, Sass를 사용하여 스타일링해보자.
// components/Button.js
import React from 'react';
import './Button.scss';
function Button({ children }) {
return <button className="Button">{children}</button>;
}
export default Button;
// components/Button.scss
$blue: #228be6;
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
background: $blue; // 주석 사용
&:hover {
background: lighten($blue, 10%); // 색상 10% 밝게
}
&:active {
background: darken($blue, 10%); // 색상 10% 어둡게
}
}
// App.js
import React from 'react';
import './App.scss';
import Button from './components/Button';
function App() {
return (
<div className="App">
<div className="buttons">
<Button>BUTTON</Button>
</div>
</div>
);
}
export default App;
이런식으로 Button 컴포넌트를 스타일링할 수 있다.
실행하면 위와 같은 화면이 나타난다.
3) 조건부로 CSS 클래스를 넣어주고 싶을때
$ yarn add classnames
// Button.js
import classNames from 'classnames';
function Button({ children, size }) {
return <button className={classNames('Button', size)}>{children}</button>;
}
Button.defaultProps = {
size: 'medium'
};
위처럼 코드를 작성하면 props 값이 button 태그의 className 으로 전달이 될 것이다.
.Button {
&.large {
}
}
.Button.large {
}
위 두 코드가 의미하는 것은 Button 과 large CSS 클래스가 함께 적용되어 있으면 원하는 스타일을 적용하겠다는 것이다.
<div className="App">
<div className="buttons">
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
</div>
이런식으로 사용하면 버튼의 크기를 조절할 수 있다.
4) 색상 추가하기
// Button.js
function Button({ children, size, color }) {
return (
<button className={classNames('Button', size, color)}>{children}</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
// Button.scss
$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);
}
}
이런식으로 사용하면 된다. 반복되는 코드는 Sass 의 mixin 기능을 사용하였다.
// App.js
function App() {
return (
<div className="App">
<div className="buttons">
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</div>
<div className="buttons">
<Button size="large" color="gray">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button size="small" color="gray">
BUTTON
</Button>
</div>
<div className="buttons">
<Button size="large" color="pink">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button size="small" color="pink">
BUTTON
</Button>
</div>
</div>
);
}
이렇게 다양한 색상의 버튼을 만들 수 있다.
- 버튼에 이벤트 넣고 관리하기
// Button.js
function Button({ children, size, color, outline, fullWidth, ...rest }) {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
{...rest}
>
{children}
</button>
);
}
이렇게 ...rest를 사용해서 지정한 props 를 제외한 값들을 rest 라는 객체에 모아주고, <button> 태그에 {...rest} 를 해주면, rest 안에 있는 객체안에 있는 값들을 모두 <button> 태그에 설정을 해준다.
// App.js
<Button size="large" onClick={() => console.log('클릭됐다!')}>
BUTTON
</Button>
이렇게 하면 Button에 onClick 이벤트를 넣을 수 있다.
이처럼 컴포넌트가 어떤 props 를 받을 지 확실치는 않지만 그대로 다른 컴포넌트 또는 HTML 태그에 전달을 해주어야 하는 상황에는 이렇게 ...rest 문법을 사용하면 된다.
2. CSS Module
1) CSS Module
리액트 프로젝트에서 컴포넌트를 스타일링 할 때 CSS Module 이라는 기술을 사용하면, CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있다.
CSS Module에서는 .module.css 확장자를 사용한다.
// Box.module.css
.Box {
background: black;
color: white;
padding: 2rem;
}
React 컴포넌트에서는 CSS 파일에 선언한 클래스 이름들이 모두 고유해진다.
// Box.js
function Box() {
return <div className={styles.Box}>{styles.Box}</div>;
}
className 을 설정 할 때에는 styles.Box 이렇게 import로 불러온 styles 객체 안에 있는 값을 참조해야 한다.
클래스 이름에 대하여 고유한 이름들이 만들어지기 때문에, 실수로 CSS 클래스 이름이 다른 관계 없는 곳에서 사용한 CSS 클래스 이름과 중복되는 일에 대하여 걱정 할 필요가 없다.
2) CSS Module 기술을 사용하여 커스텀 체크박스 컴포넌트 만들기
// components/CheckBox.js
import React from 'react';
function CheckBox({ children, checked, ...rest }) {
return (
<div>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div>{checked ? '체크됨' : '체크 안됨'}</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
// App.js
import React, { useState } from 'react';
import CheckBox from './components/CheckBox';
function App() {
const [check, setCheck] = useState(false);
const onChange = e => {
setCheck(e.target.checked);
};
return (
<div>
<CheckBox onChange={onChange} checked={check}>
다음 약관에 모두 동의
</CheckBox>
<p>
<b>check: </b>
{check ? 'true' : 'false'}
</p>
</div>
);
}
export default App;
위 프로젝트에 스타일링을 해보자.
$ yarn add react-icons
react-icons을 사용하면 원하는 아이콘을 쉽게 사용할 수 있다.
// components/CheckBox.js
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
// .
// .
<label>
<input type="checkbox" checked={checked} {...rest} />
<div>{checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}</div>
</label>
이렇게 수정하면 아이콘이 화면에 나오게 된다.
// components/CheckBox.module.css
.checkbox {
display: flex;
align-items: center;
}
.checkbox label {
cursor: pointer;
}
/* 실제 input 을 숨기기 위한 코드 */
.checkbox input {
width: 0;
height: 0;
position: absolute;
opacity: 0;
}
.checkbox span {
font-size: 1.125rem;
font-weight: bold;
}
.icon {
display: flex;
align-items: center;
/* 아이콘의 크기는 폰트 사이즈로 조정 가능 */
font-size: 2rem;
margin-right: 0.25rem;
color: #adb5bd;
}
.checked {
color: #339af0;
}
CSS Module 을 작성 할 때에는 CSS 클래스 이름이 다른 곳에서 사용되는 CSS 클래스 이름과 중복될 일이 없기 때문에 .icon, .checkbox 같은 짧고 흔한 이름을 사용해도 된다.
// components/CheckBox.js
import React from 'react';
import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md';
import styles from './CheckBox.module.css';
function CheckBox({ children, checked, ...rest }) {
return (
<div className={styles.checkbox}>
<label>
<input type="checkbox" checked={checked} {...rest} />
<div className={styles.icon}>
{checked ? (
<MdCheckBox className={styles.checked} />
) : (
<MdCheckBoxOutlineBlank />
)}
</div>
</label>
<span>{children}</span>
</div>
);
}
export default CheckBox;
classnames 라이브러리에 있는 bind 기능을 사용하면 CSS Module을 더 편하게 사용할 수 있다.
$ yarn add classnames
<input type="checkbox" checked={checked} {...rest} />
<div className={cx('icon')}>
{checked ? (
<MdCheckBox className={cx('checked')} />
위 코드처럼 classnames 의 bind 기능을 사용하면, CSS 클래시 이름을 지정해 줄 때 cx('클래스이름') 과 같은 형식으로 편하게 사용할 수 있다.
cx('one', 'two')
cx('my-component', {
condition: true
})
cx('my-component', ['another', 'classnames'])
CSS Module 은 Sass 에서도 사용 할 수 있다. 확장자를 .module.scss 로 바꿔주면 된다.
(예시)
:global {
.my-global-name {
}
}
3. styled-components
styled-components는 JS 안에서 CSS를 작성할 수 있는 라이브러리이다. (CSS in JS)
1) Tagged Template Literal
Template Literal(${} 안에 변수를 넣어 사용하는 문법)을 사용하면서도, 그 내부에 넣은 자바스크립트 값을 조회하고 싶을 때 사용된다.
const red = '빨간색';
const blue = '파란색';
function favoriteColors(texts, ...values) {
console.log(texts);
console.log(values);
}
favoriteColors`제가 좋아하는 색은 ${red}과 ${blue}입니다.`
const StyledDiv = styled`
background: ${props => props.color};
`;
2) styled-components 사용하기
$ yarn add styled-components
import React from 'react';
import styled from 'styled-components';
const Circle = styled.div`
width: 5rem;
height: 5rem;
background: black;
border-radius: 50%;
`;
function App() {
return <Circle />;
}
export default App;
위와 같이 스타일을 가진 컴포넌트를 만들 수 있다.
function App() {
return <Circle color="blue" huge />;
}
이렇게 스타일을 넣어줄 수 있다.
3) Button 만들기
// components/Button.js
import React from 'react';
import styled from 'styled-components';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
background: #228be6;
&:hover {
background: #339af0;
}
&:active {
background: #1c7ed6;
}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
function Button({ children, ...rest }) {
return <StyledButton {...rest}>{children}</StyledButton>;
}
export default Button;
// App.js
import React from 'react';
import styled from 'styled-components';
import Button from './components/Button';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
function App() {
return (
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
);
}
export default App;
4. polished의 스타일 관련 유틸 함수 사용하기
Sass 에서 lighten() 이나 dartken() 과 같은 유틸 함수를 사용하려면 polished 라이브러리를 사용하면 된다.
$ yarn add polished
1) Button 만들기
다음 코드는 기존에 색상 부분을 polished 의 유틸 함수들로 대체한 코드이다.
import { darken, lighten } from 'polished';
const StyledButton = styled.button`
/* 공통 스타일 */
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding-left: 1rem;
padding-right: 1rem;
/* 크기 */
height: 2.25rem;
font-size: 1rem;
/* 색상 */
background: #228be6;
&:hover {
background: ${lighten(0.1, '#228be6')};
}
&:active {
background: ${darken(0.1, '#228be6')};
}
/* 기타 */
& + & {
margin-left: 1rem;
}
`;
App.js
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<AppBlock>
<Button>BUTTON</Button>
</AppBlock>
</ThemeProvider>
theme을 설정하면 ThemeProvider 내부에 렌더링된 style-components로 만든 컴포넌트에서 palette를 조회하여 사용할 수 있다.
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)};
}
`;
}}
// ...
`;
<Button color="gray">BUTTON</Button>
<Button color="pink">BUTTON</Button>
위와 같이 사용할 수 있다.
const StyledButton = styled.button`
// ...
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)};
}
`;
}}
// ...
`;
<Button color="pink" size="small">
2) Dialog 만들기
// components/Dialog.js
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;
// App.js의 일부
<Dialog
title="정말로 삭제하시겠습니까?"
confirmText="삭제"
cancelText="취소"
onConfirm={onConfirm}
onCancel={onCancel}
visible={dialog}
>
3) 트랜지션 구현하기
Dialog가 나타날 때 DarkBackground 쪽에는 서서히 나타나는 fadeIn 효과를 주고, DialogBlock 에는 아래에서부터 위로 올라오는 효과를 보여주는 slideUp 효과를 주었다.
// components/Dialog.js
const fadeIn = keyframes`
from {
opacity: 0
}
to {
opacity: 1
}
`;
const fadeOut = keyframes`
from {
opacity: 1
}
to {
opacity: 0
}
`;
const slideUp = keyframes`
from {
transform: translateY(200px);
}
to {
transform: translateY(0px);
}
`;
const slideDown = keyframes`
from {
transform: translateY(0px);
}
to {
transform: translateY(200px);
}
`;
const DialogBlock = styled.div`
.
.
.
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${slideUp};
animation-fill-mode: forwards;
`;
참고 : react.vlpt.us/
'WINK-(Web & App) > React.js 스터디' 카테고리의 다른 글
[2024-2 React.js 스터디] 이서영 #3주차 (0) | 2024.11.07 |
---|---|
[2024-2 React.js 스터디 ] 김지수 #3주차 (0) | 2024.11.06 |
[2024-2 React.js 스터디 ] 이서영 #2주차 (0) | 2024.10.31 |
[2024-2 React.js 스터디] 윤아영 #2주차 (0) | 2024.10.30 |
[2024-2 React.js 스터디] 김지수 #2주차 (0) | 2024.10.30 |