리액트 컴포넌트 스타일링
css파일 생성 → 컴포넌트에서 import (가장 기본적인 방법)
스타일링시 자주 사용되는 기술들
- Sass
- CSS Module
- styled-components
1. Sass
→ CSS pre-processor
→ 복잡한 작업을 쉽게
→ 코드의 재활용성, 가독성 높여줌 (유지보수 easy)
리액트 프로젝트 생성 후 node-sass 설치
$ yarn add node-sass // Sass를 CSS로 변환해주는 역할
1-1. Button 컴포넌트 만들기
src 디렉터리에 components 디렉터리 생성
→ components 디렉터리 안에 Button.js, Button.scss 생성
기존 css에서 사용 못하는 문법들을 사용
ex. 스타일파일에서 사용할 수 있는 변수 선언 / lighten(), darken() 같은(색상을 더 밝게 혹은 어둡게) 함수 사용 가능
App 컴포넌트에서 사용
기존의 App.css를 App.scss로 이름 수정
.App {
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
}
1-2. 버튼 사이즈 조정하기
Button.js에서 defaultProps를 통해 size의 기본값을 medium으로 설정, 값은 button의 className에 넣기
설치
$ yarn add classnames
Button.defaultProps = {
sixe: 'medium'
};
className에 CSS클래스 이름을 동적으로 넣기
className={['Button', size].join(' ')}
className={`Button ${size}`}
classNames를 사용해 조건부 스타일링 시
함수의 인자에 문자열, 배열, 객체 등을 전달 → 문자열 조합
Button.scss 에서 다른 크기 지정하기
$blue: #228be6;
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
// 사이즈 관리
&.large {
height: 3rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1.25rem;
}
&.medium {
height: 2.25rem;
padding-left: 1rem;
padding-right: 1rem;
font-size: 1rem;
}
&.small {
height: 1.75rem;
font-size: 0.875rem;
padding-left: 1rem;
padding-right: 1rem;
}
background: $blue;
&:hover {
background: lighten($blue, 10%);
}
&:active {
background: darken($blue, 10%);
}
}
App.js 에서 버튼 2개 더 렌더링, size 값 설정해주기
1-3. 버튼 사이에 여백
& + & {
margin-left: 1rem;
}
& + & = .Button + .Button
1-4. 버튼 색상 설정
Button.js 에서 color 라는 props 를 받아올 수 있도록 설정, 기본값 = blue
color 값을 className 에 포함
Button.scss에서 색상 변수 선언 후 CSS 클래스에 따라 다른 색상이 적용되도록 작성
$blue: #228be6;
$gray: #495057;
$pink: #f06595;
.Button {
display: inline-flex;
color: white;
font-weight: bold;
outline: none;
border-radius: 4px;
border: none;
cursor: pointer;
// 사이즈 부분은 지웠어요
//색상
&.blue {
background: $blue;
&:hover {
background: lighten($blue, 10%);
}
&:active {
background: darken($blue, 10%);
}
}
&.gray {
background: $gray;
&:hover {
background: lighten($gray, 10%);
}
&:active {
background: darken($gray, 10%);
}
}
&.pink {
background: $pink;
&:hover {
background: lighten($pink, 10%);
}
&:active {
background: darken($pink, 10%);
}
}
& + & {
margin-left: 1rem;
}
}
&:hover {
background: lighten($pink, 10%);
}
&:active {
background: darken($pink, 10%);
}
위의 부분이 계속 반복됨 → Sass의 mixin 기능 이용
@mixin button-color($color) {
background: $color;
&:hover {
background: lighten($color, 10%);
}
&:active {
background: darken($color, 10%);
}
}
&.blue {
@include button-color($blue);
} //훨씬 간편해짐
App.js 에서 다른 색상 가진 버튼들도 렌더링
1-5. outline 옵션 만들기
버튼에서 테두리만 보여지도록 설정
Button.js 에서 outline 값을 props 받아온 후 객체 안에 집어넣은 다음에 classNames() 에 포함
→ outline 값이 true 일 때만 button 에 outline CSS 클래스 적용
mixin을 이용해 outline
@mixin button-color($color){
&.outline {
color: $color;
background: none;
border: 1px solid $color;
&.hover {
background: $color;
color: white;
}
}
}
전체 너비 차지하는 옵션
→ fullWidth ( 구현 방식은 outline과 유사 )
1-6. 버튼 컴포넌트에 onClick 설정
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function Button({ children, size, color, outline, fullWidth, onClick }) {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
onClick={onClick}
>
{children}
</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
onMouseMove 이벤트를 관리하고 싶다면? 또 추가
~싶다면? 추가
~싶다면? 추가 .....
귀찮음
→ spread, rest 문법 사용
import React from 'react';
import classNames from 'classnames';
import './Button.scss';
function Button({ children, size, color, outline, fullWidth, ...rest }) {
return (
<button
className={classNames('Button', size, color, { outline, fullWidth })}
{...rest}
>
{children}
</button>
);
}
Button.defaultProps = {
size: 'medium',
color: 'blue'
};
export default Button;
...rest 를 사용해서 지정한 props 를 제외한 값들을 rest라는 객체에 모으고
<button> 태그에 {...rest}를 하면 rest 안에 있는 객체 안의 값들을 모두 버튼 태그에 설정 해줌
2. CSS Module
→ 레거시 프로젝트에 리액트 도입
→ CSS 클래스가 중첩되는 것을 방지
리액트 컴포넌트 파일에서 CSS 파일에 선언한 클래스 이름들이 모두 고유해짐
고유 CSS 클래스 이름이 만들어지는 과정에서 파일 경로, 파일 이름, 클래스 이름, 해쉬값 등이 사용
CSS 클래스 네이밍 규칙 ex.
- 컴포넌트의 이름은 다름 컴포넌트랑 중복되지 않게 한다.
- 컴포넌트의 최상단 CSS 클래스는 컴포넌트의 이름과 일치시킨다.
- 컴포넌트 내부에서 보여지는 CSS 클래스는 CSS Selector를 잘 활용한다.
CSS 클래스 네이밍 규칙을 만들고 따르기 싫다 → CSS Module
2-1. CSS Module 기술을 사용하여 커스텀 체크박스 컴포넌트를 만들어보기
CSS Module 은 별도로 설치해야 할 라이브러리 x
src 디렉터리 → 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 컴포넌트에서 렌더링 시, 체크박스 뿐만 아니라 '체크 안됨' 글씨를 눌러도 변하는 걸 확인할 수 있음
bc. label태그로 감싸주었기 때문
스타일링
react-icons 라이브러리 설치
→ Font Awesome, Ionicons, Material Design Icons 등의 아이콘들을 컴포넌트 형태로 사용 가능
위의 셋 중 Material Design Icons의 MdCheckBox, MdCheckBoxOutline 사용
$ yarn add react-icons
components 디렉터리에 CheckBox.module.css 파일 생성 후 다음 코드 입력
/*
CSS Module 작성시, CSS 클래스 이름이 다른 곳에서 사용되는 CSS 클래스 이름과 중복 X
따라서 .icon .checkbox 같은 짧고 흔한 이름 사용해도 됨
*/
.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;
}
Check.Box.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;
CSS Module 사용 시 객체 안의 값을 조회해야할 때가 있음
classnames의 bind 기능으로 편리하게
$ yarn add classnames
clasdsnames의 bind 기능으로 CSS 클래스 이름 지정시 cs('클래스이름') 같은 형식으로 사용 가능
CSS Module은 node-sass 설치 후 확장자를 .module.scss로 바꿔주면 Sass 에서 사용 가능
3. styled-components
→ CSS in JS 사용 ( JS 안에 CSS 작성 )
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;
styled-components → 스타일을 입력함과 동시에 스타일을 가진 컴포넌트 생성 가능
ex. div 스타일링 → styled.div / input 스타일링 → styled.input
Circle 컴포넌트에 color props
import React from 'react';
import styled from 'styled-components';
const Circle = styled.div`
width: 5rem;
height: 5rem;
background: ${props => props.color || 'black'};
border-radius: 50%;
`;
function App() {
return <Circle color="blue" />;
}
export default App;
huge props 설정해서 크기 변경 가능
const Circle = styled.div
background: ${props => props.color || 'black'};
border-radius: 50%;
${props =>
props.huge &&
css`
width: 10rem;
height: 10rem;
` }
`;
3-1. Button 만들기
src 디렉터리 안에 components 디렉터리 생성 후 Button.js 파일 생성
Sass에서 lighten() 또는 darken() 같은 유틸함수로 색상에 변화를 줄 수 있었음
→ CSS in JS 에서 비슷한 유틸함수를 사용하려면 polished 라이브러리 사용
패키지 설치 설치의 연속!
$ yarn add polished
3-2. 색상 변경하기
색상 코드를 지닌 변수를 Button.js 에서 선언
대신에 ThemeProvider 기능 사용,
styled-components로 만드는 모든 컴포넌트에서 조회하여 사용할 수 있는 전역적인 값 설정
3-3. Dialog 만들기
components 디렉터리에 Dialog.js 파일 생성
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;
`;
function Dialog({ title, children, confirmText, cancelText }) {
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<ButtonGroup>
<Button color="gray">{cancelText}</Button>
<Button color="pink">{confirmText}</Button>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
};
export default Dialog;
3-4. 트랜지션 구현
Dialog 가 나타나거나 사라질 때 트랜지션 효과 적용
→ CSS Keyframe 사용
→ styled-components 에서는 keyframes 유틸 사용
import React, { useState, useEffect } from 'react';
import styled, { keyframes, css } from 'styled-components';
import Button from './Button';
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 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);
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${fadeIn};
animation-fill-mode: forwards;
${props =>
props.disappear &&
css`
animation-name: ${fadeOut};
`}
`;
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;
}
animation-duration: 0.25s;
animation-timing-function: ease-out;
animation-name: ${slideUp};
animation-fill-mode: forwards;
${props =>
props.disappear &&
css`
animation-name: ${slideDown};
`}
`;
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
}) {
const [animate, setAnimate] = useState(false);
const [localVisible, setLocalVisible] = useState(visible);
useEffect(() => {
// visible 값이 true -> false 가 되는 것을 감지
if (localVisible && !visible) {
setAnimate(true);
setTimeout(() => setAnimate(false), 250);
}
setLocalVisible(visible);
}, [localVisible, visible]);
if (!animate && !localVisible) return null;
return (
<DarkBackground disappear={!visible}>
<DialogBlock disappear={!visible}>
<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;
'WINK-(Web & App) > React.js 스터디' 카테고리의 다른 글
[2024-2 React.js 스터디] 윤아영 #3주차 (0) | 2024.11.06 |
---|---|
[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 |