일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 코딩기초
- 비동기
- async
- 리덕스
- SasS
- CSS
- React Native
- redux
- 사용하는 이유
- 타입스크립트
- 코딩초보
- html기초
- 자바스크립트
- react-router
- 리액트 네이티브
- 프론트엔드
- Vue3
- scss
- 코린이
- 깃
- 리액트
- 코딩독학
- useEffect
- 참조자료형
- react
- http
- git
- TypeScript
- JavaScript
- 코딩공부
- Today
- Total
맨 땅에 프론트엔드 개발자 되기
styled-components를 이용하여 스타일링 변경이 가능한 컴포넌트 만들기 본문
styled-components를 사용하는 이유
CSS in JS 는 웹 개발 방식의 한 방법으로 JS 파일 안에 CSS 작성해서 경우에 따라 JS 로 CSS 변경을 용이하게 만들어 스타일링 변경이 가능한 컴포넌트를 사용할 수 있게 해주는 하나의 방법이다.
Tagged Template Literal을 이용하여 함수의 파라미터로 템플릿 리터럴을 받는 기술인데 복잡하므로 원리를 이해할 필요는 없다.
npm i styled-components
기본 문법
import styled from 'styled-components';
// Create a Title component that'll render an <h1> tag with some styles
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
// Use Title and Wrapper like any other React component – except they're styled!
function App() {
return (
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
export default App;
}
styled-components 를 처음 사용하면서 헷갈렸던 것은 기존에 JSX 문법으로 컴포넌트를 불러와 return을 시키는 것이 아니라 html 태그 대신 스타일링이 적용된 컴포넌트를 상단에 구성하고 JSX 문법으로 사용하는 것이었다.
CSS 문법과 매우 유사하고 SASS와 같이 nesting 문법이 사용 가능하기 때문에 조금만 익숙해지면 매우 쉽게 사용할 수 있다.
styled-components를 이용해서 재사용 가능한 버튼 컴포넌트 만들기
// ./components/Button.jsx
import styled, { css } from 'styled-components';
import { darken, lighten } from 'polished';
const colorStyles = css`
${({theme, color}) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.1, selected)};
}
&:active {
background: ${darken(0.1, selected)}
}
${props => props.outline &&
css`
color: ${selected};
background: none;
border: 1px solid ${selected};
&:hover {
background: ${selected};
color: white;
}
`}
`;
/*
props => {
const selected = props.theme.palette[props.color]
}
*/
}}
`;
const sizes = {
large: {
padding: '1.5rem 2rem',
fontSize: '1.5rem'
},
medium: {
padding: '1rem 1.5rem',
fontSize: '1.25rem'
},
small: {
padding: '0.7rem 1rem',
fontSize: '1rem'
}
};
const sizeStyles = css`
${({size}) => css`
padding: ${sizes[size].padding};
font-size: ${sizes[size].fontSize};
`}
`;
const fullWidthStyle = css`
${props => props.fullWidth &&
css`
width: 100%;
justify-content: center;
&:not(:first-child) {
margin-left: 0;
margin-top: 1rem;
}
`}
`;
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;
/* 크기 */
${sizeStyles}
/* 색상 */
${colorStyles}
/* 기타 */
&:not(:first-child) {
margin-left: 1rem;
}
${fullWidthStyle}
`;
Button.defaultProps = {
color: 'blue',
size: 'medium'
}
export default function Button({children, color, size, outline, fullWidth, ...rest}) {
return <StyledButton
color={color}
size={size}
outline={outline}
fullWidth={fullWidth}
{...rest}>
{children}
</StyledButton>
}
styled-components를 이용해 color, size, outline, fullWidth 적용 여부를 선택할 수 있는 스타일링 변경이 가능한 컴포넌트가 만들어졌다. (위의 예제는 '벨로퍼트와 함께 하는 모던 리액트'를 참고해서 공부했습니다.)
위의 컴포넌트를 가져다가 사용할 때는 아래와 같이 사용할 수 있다.
// App.jsx
import { useState } from 'react';
import styled, { css, ThemeProvider } from 'styled-components';
import Button from './components/Button';
const Block = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const ButtonGroup = styled.div`
&:not(:first-child) {
margin-top: 1rem;
}
`;
export default function Styled() {
const [dialog, setDialog] = useState(false);
const onClick = () => {
setDialog(true);
}
const onConfirm = () => {
setDialog(false);
}
const onCancel = () => {
setDialog(false);
}
return (
<main>
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<Block>
<ButtonGroup>
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="gray" size="large">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button color="gray" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="pink" size="large">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button color="pink" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button color="pink" size="small" outline>
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" color="gray" fullWidth>
BUTTON
</Button>
<Button size="large" color="pink" fullWidth onClick={onClick}>
삭제
</Button>
</ButtonGroup>
</Block>
</ThemeProvider>
</main>
)
}
styled-components로 애니메이션 적용하기
styled-components를 이용하여 모달 창 애니메이션을 구현해보려고 한다.
일단, 먼저 모달 창 컴포넌트의 코드는 아래와 같다.
// ./components/Dialog.jsx
import { useEffect, useState } 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)`
&:not(:first-child) {
margin-left: 0.5rem;
}
`;
Dialog.defaultProps = {
confirmText: '확인',
cancelText: '취소'
}
export default 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"
size="small"
onClick={onCancel}>
{cancelText}
</ShortMarginButton>
<ShortMarginButton
color="pink"
size="small"
onClick={onConfirm}>
{confirmText}
</ShortMarginButton>
</ButtonGroup>
</DialogBlock>
</DarkBackground>
);
}
styled-components 에서 애니메이션을 적용하기 위해서는 keyframes를 import 해서 사용할 수 있다. state 값을 설정해 상태 값에 따라 모달 창이 보였다가 사라지게 구현하는 기본 원리를 두고, 거기에 localVisible 이라는 상태 값을 모달 창 컴포넌트에 내부적으로 추가해 setTimeout을 이용하여 상태값을 관리한다.
모달 창을 불러오는 곳에서는 아래와 같이 작성한다.
// App.jsx
import { useState } from 'react';
import styled, { ThemeProvider } from 'styled-components';
import Button from './components/Button';
import Dialog from './components/Dialog';
const Block = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4rem;
border: 1px solid black;
padding: 1rem;
`;
const ButtonGroup = styled.div`
&:not(:first-child) {
margin-top: 1rem;
}
`;
export default function Styled() {
const [dialog, setDialog] = useState(false);
const onClick = () => {
setDialog(true);
}
const onConfirm = () => {
setDialog(false);
}
const onCancel = () => {
setDialog(false);
}
return (
<main>
<ThemeProvider
theme={{
palette: {
blue: '#228be6',
gray: '#495057',
pink: '#f06595'
}
}}
>
<Block>
<ButtonGroup>
<Button size="large">BUTTON</Button>
<Button>BUTTON</Button>
<Button size="small">BUTTON</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="gray" size="large">
BUTTON
</Button>
<Button color="gray">BUTTON</Button>
<Button color="gray" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button color="pink" size="large">
BUTTON
</Button>
<Button color="pink">BUTTON</Button>
<Button color="pink" size="small">
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" outline>
BUTTON
</Button>
<Button color="gray" outline>
BUTTON
</Button>
<Button color="pink" size="small" outline>
BUTTON
</Button>
</ButtonGroup>
<ButtonGroup>
<Button size="large" fullWidth>
BUTTON
</Button>
<Button size="large" color="gray" fullWidth>
BUTTON
</Button>
<Button size="large" color="pink" fullWidth onClick={onClick}>
삭제
</Button>
</ButtonGroup>
</Block>
<Dialog
title="정말로 삭제하시겠습니까?"
confirmText="삭제"
onConfirm={onConfirm}
onCancel={onCancel}
visible={dialog}>
데이터를 정말로 삭제하시겠습니까?
</Dialog>
</ThemeProvider>
</main>
)
}
삭제 버튼을 누르면 모달 창이 보이고, '삭제' 혹은 '취소' 버튼을 누르면 모달 창이 사라지도록 구현되었다.
사용 후기
프로젝트에서 공통으로 사용되는 컴포넌트가 있을 경우, CSS 파일이 분리되어 있다면 JS로 수정하기가 불편했던 점이 있었는데, styled-components를 이용해서 수정이 가능하게 되어 컴포넌트 재사용이 훨씬 용이해진 것을 느낄 수 있었다.
'코딩 공부 일지 > React JS' 카테고리의 다른 글
리액트에서 useRef를 이용해서 현명하게 setInterval 사용하기 (0) | 2022.05.25 |
---|---|
CRA로 생성된 프로젝트에 절대경로 설정하기 (0) | 2022.05.06 |
리액트 React 에서 탭 기능 구현하기 (0) | 2022.04.17 |
useParams 로 세부 페이지 라우팅 구현하기 (0) | 2022.04.14 |
React useEffect와 addEventListener - window 이벤트 렌더링 규칙 (0) | 2022.04.04 |