1.Styled-Component 코드 리팩토링
2.Polish 사용
저번에 만들었던 styled-component코드를 조금 정리하고 hover 되었을 때 파란색으로만 변하는 효과를
Polished 를 사용해서 고쳐주었다.
ButtonTotal.js
import React from "react";
import styled, { css } from "styled-components";
const ButtonTotal = ({ children, size, color, ...rest }) => {
const StyledButton = styled.button`
/*공통스타일*/
display: inline-flex;
outline: none;
border: none;
border-radius: 4px;
color: white;
font-weight: bold;
cursor: pointer;
padding: 2em;
margin: 10px;
/*크기*/
font-size: 1em;
/* 조건별색깔 */
${({ theme, color }) => {
const selectcolor = theme.palette[color];
return css`
background: ${selectcolor};
`;
}}
/*조건별크기*/
${(props) => {
return (
props.size === "large" &&
css`
height: 3.5em;
font-size: 1.5em;
width: 40%;
`
);
}}
${(props) => {
return (
props.size === "medium" &&
css`
height: 2.25em;
font-size: 1em;
`
);
}}
${(props) => {
return (
props.size === "small" &&
css`
height: 1.5em;
font-size: 0.75em;
`
);
}}
/*효과*/
&:hover {
background: #1c7ed6;
}
`;
return (
<StyledButton size={size} color={color} {...rest}>
{children}
</StyledButton>
);
};
ButtonTotal.defaultProps = {
size: "medium",
color: "blue"
};
export default ButtonTotal;
전에썻던 코드이다 StyledButton 쪽 코드에 조건으로 주는 값이 많아서 읽기 불편한 것 같다.
/*조건별크기*/
${(props) => {
return (
props.size === "large" &&
css`
height: 3.5em;
font-size: 1.5em;
width: 40%;
`
);
}}
${(props) => {
return (
props.size === "medium" &&
css`
height: 2.25em;
font-size: 1em;
`
);
}}
${(props) => {
return (
props.size === "small" &&
css`
height: 1.5em;
font-size: 0.75em;
`
);
}}
위의 이 코드를 지우고 스타일 컴포넌트 윗줄코드에
// 사이즈
const sizes ={
large:{
height: '3em',
fontSize: '1.25em',
width:'40%'
},
medium:{
height: '2.5em',
fontSize: '1em',
width:'25%'
},
small:{
height: '1.75em',
fontSize: '1em',
width:'15%'
},
}
const sizestyle = css`
${({size})=>css`
height:${sizes[size].height};
font-size:${sizes[size].fontSize};
width:${sizes[size].width};
`}
`;
이렇게 사이즈로 들어갈 sizes객체를 하나 만들고ButtonTotal에 props로 받아오는 size를 css로 작성해서 sizestyle에 저장해 준다. 그러면 스타일이 변수에 저장되게 되고 변수를 스타일 컴포넌트에 넣어주면 스타일 컴포넌트가 만들어지기 전에 미리 조건으로 스타일을 정해서 컴포넌트에 넣어주게 된다.
색깔지정코드
/* 조건별색깔 */
${({ theme, color }) => {
const selectcolor = theme.palette[color];
return css`
background: ${selectcolor};
`;
}}
이 코드도 스타일 컴포넌트 밖으로 빼서 변수에 css를 저장해 준 뒤에
저장된 변수만 스타일컴포넌트에 넣어주겠다.
const colorStyle=css`
${({theme,color})=>{
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background:${lighten(0.2,selected)}
}
`;
}}`;
위와 똑같이 스타일컴포넌트가 만들어지기 전에 ButtonTotal에서 받은 props를 이용해서 스타일을 만들고 css를 colorstyle에 저장해 주었다
완성된 코드
import React from 'react';
import styled,{css} from 'styled-components';
import { lighten } from 'polished';
const ButtonTotal = ({children,color,size,fullWidth,...res}) => {
//배경색변수
const colorStyle=css`
${({theme,color})=>{
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background:${lighten(0.2,selected)}
}
`;
}}`;
// 사이즈
const sizes ={
large:{
height: '3em',
fontSize: '1.25em',
width:'40%'
},
medium:{
height: '2.5em',
fontSize: '1em',
width:'25%'
},
small:{
height: '1.75em',
fontSize: '1em',
width:'15%'
},
}
const sizestyle = css`
${({size})=>css`
height:${sizes[size].height};
font-size:${sizes[size].fontSize};
width:${sizes[size].width};
`}
`;
// 스타일컴포넌트
const StyledButton = styled.button`
/*공통스타일*/
display:inline-flex;
outline: none;
border:none;
border-radius:4px;
color:white;
font-weight:bold;
cursor:pointer;
padding: 1em;
justify-contents:center;
/*크기*/
font-size:1em;
margin: 0.5em;
align-items:center;
/*색상*/
${colorStyle}
/*크기스타일*/
${sizestyle}
//전체너비 100%스타일
${props=>{
return props.fullWidth &&
css `
width:100%;
`;
}}
&+&{
margin-left: 1em;
}
`;
return (
<StyledButton color={color} size={size} fullWidth={fullWidth} {...res}>{children}</StyledButton>
);
};
ButtonTotal.defaultProps={
color:'blue',
size:'medium'
}
export default ButtonTotal;
코드를 보면 스타일컴포넌트를 만들기 전에 변수에 css를 저장하고 css가 저장된 변수를 스타일 컴포넌트에 넣어서 사용해 준다. ${colorStyle} , ${sizestyle}
꼭 이렇게 코드를 작성하는 게 더 좋다는 건 아니고 이러면 보기에 조금 깔끔해 보이기는 하는데
내 개인적인 생각으로는 스타일 컴포넌트 자체가 모든 스타일을 컴포넌트 안에 정의해 줘서 코드를 간결하게 해 주는데 이렇게 밖에서 만들어서 적용해 주면 저 변수에 어떤 게 들어가는지 또 찾아가서 봐야 하고 좀 '굳이?'라는 생각이 들긴 한다. 취향인 쪽을 쓰거나 취업을 한다면 사수가 원하는 방향으로 써주면 되겠지... 그래도 나는 안에 다 들어있는 게 좋다고 생각함 왔다 갔다 하는 거 진짜 너무 힘들다.. 컴포넌트도 많아서 걔네들 돌아가면서 보는 것도 힘든데..
Polished 라이브러리-- wooah(우아한..)
저번에 sass와 styled-component를 설치할 때처럼 사용하려는 프로젝트 폴더로 이동해서 설치해 준다
npm install --save polished
이제 polish에 있는 기능을 import 해서 사용해 주면 된다. 여기서는 lighten이라는 기능을 사용해 줄 것이다.
https://polished.js.org/docs/#lighten
스타일컴포넌트 아래에 있던
&:hover {
background: #1c7ed6;
}
요기를 지워주고
colorstyle css에 같이 넣어주겠다.
import { lighten } from 'polished';
const ButtonTotal = ({children,color,size,fullWidth,...res}) => {
//배경색변수
const colorStyle=css`
${({theme,color})=>{
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background:${lighten(0.2,selected)}
}
`;
}}`;
여기 완성코드의 위쪽 부분을 잘라온 건데 lighten을 import 해주고 colorStyle을 만들 때 색을 받아오기 때문에
colorstyle안에다 적어주고 지정된 색이 들어있는selected를 같이 사용해 주고 밝기를 조절해 준다
이제 여기에 삭제 버튼을 박스 안에 하나 추가해 주고 삭제버튼을 추가하면 대화창이 나오게 해 주겠다.
App.js
import "./styles.css";
import styled, { ThemeProvider } from "styled-components";
import ButtonTotal from "./ButtonTotal";
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4em;
border: 1px solid black;
padding: 1em;
`;
export default function App() {
return (
<ThemeProvider
theme={{
palette: {
blue: "#228be6",
gray: "#495057",
pink: "#f06595"
}
}}
>
<AppBlock>
<div>
<ButtonTotal size="large">Button</ButtonTotal>
<ButtonTotal>Button</ButtonTotal>
<ButtonTotal size="small">Button</ButtonTotal>
</div>
<div>
<ButtonTotal size="large" color="pink">
Button
</ButtonTotal>
<ButtonTotal color="pink">Button</ButtonTotal>
<ButtonTotal color="pink" size="small">
Button
</ButtonTotal>
</div>
<div>
<ButtonTotal size="large" color="gray">
Button
</ButtonTotal>
<ButtonTotal color="gray">Button</ButtonTotal>
<ButtonTotal color="gray" size="small">
Button
</ButtonTotal>
</div>
<div>
<ButtonTotal fullWidth>Button</ButtonTotal>
</div>
<div>
<ButtonTotal fullWidth color="pink">
삭제
</ButtonTotal>
</div>
</AppBlock>
</ThemeProvider>
);
}
ButtonTotal컴포넌트를 하나 더 넣고 children으로 "삭제"를 넣어준다
이제 저 삭제 버튼을 누르면 화면이 어두워지고 대화창? 같은 게 뜨도록 해주겠다.
Dialog.js
import React from 'react';
import styled from 'styled-components';
import ButtonTotal from './ButtonTotal';
//배경 컴포넌트
const DarkBackground = styled.div`
position:fixed;
left: 0;
top:0;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items:center;
background: rgba(0,0,0,0.8);
`;
// 컨펌창 블럭
const DialogBlock = styled.div`
width:320px;
padding: 1.5em;
background: white;
border-radius: 2px;
h3{ //DialogBlock안에 있는 h3태그에적용
margin: 0;
font-size: 1.5em;
}
p{ //DialogBlock안에 있는 p태그에 적용
font-size: 1.125em;
}
`;
const Dialog = ({title,children,confirmText,cancelText}) => {
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<div>
<ButtonTotal color="gray">{confirmText}</ButtonTotal>
<ButtonTotal color="pink"}>{cancelText}</ButtonTotal>
</div>
</DialogBlock>
</DarkBackground>
);
};
export default Dialog;
어두운 배경이 될 DarkBackground컴포넌트를 만들고 그 안에
확인창이 될 DialogBlock 컴포넌트를 만들어서 넣어줬다.
app.js에서 props로 {title, children, confirmText, cancelText}를 받아와서 내용으로 넣어준다.
ButtonTotal 컴포넌트를 import 해서 버튼을 만들어준다.
App.js
import "./styles.css";
import styled, { ThemeProvider } from "styled-components";
import ButtonTotal from "./ButtonTotal";
import Dialog from "./Dialog";
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4em;
border: 1px solid black;
padding: 1em;
`;
export default function App() {
return (
<ThemeProvider
theme={{
palette: {
blue: "#228be6",
gray: "#495057",
pink: "#f06595"
}
}}
>
<AppBlock>
<div>
<ButtonTotal size="large">Button</ButtonTotal>
<ButtonTotal>Button</ButtonTotal>
<ButtonTotal size="small">Button</ButtonTotal>
</div>
<div>
<ButtonTotal size="large" color="pink">
Button
</ButtonTotal>
<ButtonTotal color="pink">Button</ButtonTotal>
<ButtonTotal color="pink" size="small">
Button
</ButtonTotal>
</div>
<div>
<ButtonTotal size="large" color="gray">
Button
</ButtonTotal>
<ButtonTotal color="gray">Button</ButtonTotal>
<ButtonTotal color="gray" size="small">
Button
</ButtonTotal>
</div>
<div>
<ButtonTotal fullWidth>Button</ButtonTotal>
</div>
<div>
<ButtonTotal fullWidth color="pink">
삭제
</ButtonTotal>
</div>
</AppBlock>
<Dialog
title="정말로 삭제하시겠습니까"
confirmText="삭제"
cancelText="취소"
>
데이터를 정말로 삭제하시겠습니까
</Dialog>
</ThemeProvider>
);
}
Dialog 컴포넌트도 내부에서 쓰고 있는 ButtonTotal컴포넌트에서 ThemeProvider값을 쓰고 있기 때문에
안쪽에 마운트 해준다.
Dialog에 props로 title , confirmText , cacelText , 그리고 태그 사이에 있는 값인 children을 전송해 준다.
지금은 아무것도 누르지 않았는데 Dialog가 마운트 되어서 보이게 된다. 이걸 삭제버튼을 클릭하면 mount가 보이도록 설정하고 삭제나 취소를 누르면 다시 사라지도록 해주겠다.
App.js
import "./styles.css";
import styled, { ThemeProvider } from "styled-components";
import ButtonTotal from "./ButtonTotal";
import Dialog from "./Dialog";
import {useState} from 'react';
const AppBlock = styled.div`
width: 512px;
margin: 0 auto;
margin-top: 4em;
border: 1px solid black;
padding: 1em;
`;
export default function App() {
const[dialog,setDialog]=useState(false); //dialog를 보이고 안보이게 해줄 상태값 false일때 안보임
function onClick(){
setDialog(true); //함수실행되면 true로 바뀌면서 dialog가 보이게 해줌
}
const onConfirm = ()=>{
console.log('확인')
//확인 버튼을 누르면 false가 되고 dialog가 사라짐
setDialog(false)
}
const onCancel = ()=>{
console.log('취소')
//취소 버튼을 누르면 false가 되고 dialog가 사라짐
setDialog(false)
}
return (
<ThemeProvider
theme={{
palette: {
blue: "#228be6",
gray: "#495057",
pink: "#f06595"
}
}}
>
<AppBlock>
<div>
<ButtonTotal size="large">Button</ButtonTotal>
<ButtonTotal>Button</ButtonTotal>
<ButtonTotal size="small">Button</ButtonTotal>
</div>
<div>
<ButtonTotal size="large" color="pink">
Button
</ButtonTotal>
<ButtonTotal color="pink">Button</ButtonTotal>
<ButtonTotal color="pink" size="small">
Button
</ButtonTotal>
</div>
<div>
<ButtonTotal size="large" color="gray">
Button
</ButtonTotal>
<ButtonTotal color="gray">Button</ButtonTotal>
<ButtonTotal color="gray" size="small">
Button
</ButtonTotal>
</div>
<div>
<ButtonTotal fullWidth>Button</ButtonTotal>
</div>
<div>
<ButtonTotal onClick={onClick} fullWidth color="pink">
삭제
</ButtonTotal>
</div>
</AppBlock>
<Dialog
title="정말로 삭제하시겠습니까"
confirmText="삭제"
cancelText="취소"
dialog={dialog}
onConfirm={onConfirm}
onCancel={onCancel}
>
데이터를 정말로 삭제하시겠습니까
</Dialog>
</ThemeProvider>
);
}
App에서 Dialog를 보이거나 보이지 않게 해 줄 상태값을 만들고 그 값을 변경하는 함수를 만들어서 Dialog에 props로 보내준다 onClick 함수는 삭제 버튼이 클릭되면 실행되어야 하기 때문에 ButtonTotal컴포넌트에 넣어준다.
ButtonTotal.js
import React from "react";
import styled, { css } from "styled-components";
import { 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: 1em;
justify-contents:center;
/*크기*/
font-size:1em;
margin: 0.5em;
align-items:center;
/*색상*/
${(props) => props.colorstyle}
/*크기스타일*/
${(props) => props.sizestyle}
//전체너비 100%스타일
${(props) => {
return (
props.fullWidth &&
css`
width: 100%;
`
);
}}
&+&{
margin-left: 1em;
}
`;
const ButtonTotal = ({ children, color, size, fullWidth, ...res }) => {
//배경색변수
const colorstyle = css`
${({ theme, color }) => {
const selected = theme.palette[color];
return css`
background: ${selected};
&:hover {
background: ${lighten(0.2, selected)};
}
`;
}}
`;
// 사이즈
const sizes = {
large: {
height: "3em",
fontSize: "1.25em",
width: "40%"
},
medium: {
height: "2.5em",
fontSize: "1em",
width: "25%"
},
small: {
height: "1.75em",
fontSize: "1em",
width: "15%"
}
};
const sizestyle = css`
${({ size }) => css`
height: ${sizes[size].height};
font-size: ${sizes[size].fontSize};
width: ${sizes[size].width};
`}
`;
return (
<StyledButton
color={color}
size={size}
fullWidth={fullWidth}
colorstyle={colorstyle}
sizestyle={sizestyle}
{...res}
>
{children}
</StyledButton>
);
};
ButtonTotal.defaultProps = {
color: "blue",
size: "medium"
};
export default ButtonTotal;
ButtonTotal컴포넌트에서는 보내주는 onClick을 받아야 하기 때문에... res로 받고 스타일 버튼에도 다시 Props를 {... res}로 넘겨준다
Dialog.js
import React from "react";
import styled from "styled-components";
import ButtonTotal from "./ButtonTotal";
//배경 컴포넌트
const DarkBackground = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.8);
`;
// 컨펌창 블럭
const DialogBlock = styled.div`
width: 320px;
padding: 1.5em;
background: white;
border-radius: 2px;
h3 {
margin: 0;
font-size: 1.5em;
}
p {
font-size: 1.125em;
}
`;
const Dialog = ({
title,
children,
confirmText,
cancelText,
dialog,
onCancel,
onConfirm
}) => {
if (!dialog) return null; //dialog가 false면 null을 리턴하고 아래의 리턴은 읽지않는다.
return (
<DarkBackground>
<DialogBlock>
<h3>{title}</h3>
<p>{children}</p>
<div>
<ButtonTotal color="gray" onClick={onConfirm}>
{confirmText}
</ButtonTotal>
<ButtonTotal color="pink" onClick={onCancel}>
{cancelText}
</ButtonTotal>
</div>
</DialogBlock>
</DarkBackground>
);
};
export default Dialog;
App에서 보내준 상태값과 상태값을 바꾸는 함수들을 Dialog에서 받아서 함수는 버튼에 클릭이벤트로 넣고 dialog는
조건문으로 dialog가 true일 때는 return null 이 되게 조건을 준다. 그러면 dialog에서 null을 return 해주기 때문에 결과적으로 아무것도 보이지 않는다.
삭제버튼을 클릭하면 dialog가 뜨고 확인이나 취소를 클릭하면 dialog가 사라지는 걸 확인할 수 있다.
이 제거의 다 왔다.. dialog가 생기고 없어질 때 지연시간을 주고 생길 때는 아래에서 올라오게 해주고 사라질 때는 아래로 내려가는 애니메이션 효과를 주겠다. 지연시간을 주려면 처음에 생길때는 크게 상관없지만 사라질때는 useEffect를 이용해서 값이 변경될 때 애니메이션을 실행시켜 주고 지연시간뒤에 애니메이션을 지워준뒤에 dialog가 사라지도록 해줘야 한다..
나도 아직 완전히 이해한 게 아니라서 최대한 풀어서 설명해도 이 정도가 나의 최선...
바로 시작!
Dialog.js
styled-components에서 keyframes를 import해준다음 애니메이션을 만들어준다.
컴포넌트 안에 animation이름을 넣어주고 애니메이션실행시간(animation-duration) 애니메이션 끝난 후상태(animation-fill-mode)를 설정해 줬다 지금은 컴포넌트가 생길 때 실행될 애니메이션만 추가되어 있고 사라질 때 실행될 애니메이션도 추가해 주겠다.
Dialog에서 스타일컴포넌트에 disappear라는 props를 보내주고 disappear가 true이면 아래의 css코드가 적용되면서 윗줄에 있는 animation-name을 덮어써준다.
이렇게 disappear props를 전달해 주는데 dialog가 false가 될 때(창이 사라질 때) → disappear는 true가 되면서 slideDown, fadeOut 애니메이션이 실행되게 해 준다.
이렇게 하면 실행될 것 같지만 실행되지 않는다
애니메이션이 실행되기 전에 이미 return null이 돼서 사라져 있기 때문..
그래서 애니메이션을 실행한 후에 사라지도록 조건과 값을 주겠다.
조건을 주기 위해 Dialog컴포넌트 안에 상태값(State)을 2개 만들어줬다 animate , localdialog
animate
dialog가 false가 돼서 사라질 때(사라지는 애니메이션 실행) true가 되고
애니메이션의 끝나는 시간인(animation-duration) 0.5초 뒤에 false가 되게 해 준다.
(사라지는 애니메이션 지속시간 동안만 true가 되는 상태값)
localdialog
지금은 확인이나 취소버튼을 클릭해서 dialog가 false로 바뀌면 바로 return null이 되는데
바로 return null이 되지 않게 해줘야 한다.
localdialog에 dialog값(true)을 넣어준다 dialog가 false로 바뀌고 localdialog가 true일 때는
return null을 하지 않고
animate상태값을 true로 만들어주고 사라지는 애니메이션을 0.5초 실행 후 animate값을 false로 바꿔준다
그 후에 localdialog값(true)도 dialog값(false)과 똑같이 만들어주고
만약localdialog와 animate 가 둘 다 false라면 null을 리턴해준다.
if(localdialog && !dialog){ //확인이나취소버튼을 클릭해서 dialog가 false로 set된 시점
setAnimate(true); //사라지는 애니메이션실행
setTimeout(()=>setAnimate(false),500); //사라지는 애니메이션 종료시점 0.5초 뒤
}
setlocaldialog(dialog); //로컬dialog은false가 됨
if(!animate && !localdialog) return null; //fadeout,slideDown애니메이션이 끝나고 localdialog가 false면 null을 리턴
이런 조건문이 나오는데 이게 실행되어야 하는 시점이 dialog값이 변하는 시점에 실행시켜 주면 된다.
값이 변하는 시점에 실행시켜주는 hook으로 useEffect를 사용해 준다
useEffect(()=>{
//dialog값이 true에서 false로 바뀔때를확인. (확인이나취소버튼클릭하면)
if(localdialog && !dialog){
setAnimate(true); //fadeout,slideDown 애니메이션 실행중~
setTimeout(()=>setAnimate(false),500); //fadeout,slideDown 애니메이션 실행끝~
}
setlocaldialog(dialog); //localdialog는false가 됨
},[localdialog,dialog]) //localdialog,dialog값이 변하는시점에 실행
if(!animate && !localdialog) return null; //fadeout,slideDown애니메이션이 끝나고 localdialog가 false면 null을 리턴
이렇게 길고 긴 styled-component 프로젝트가 끝났다.. 마지막에 useEffect가 압권이긴 했는데 리액트에 점점 익숙해져서 조금씩 이해가 되는 것 같아서 기분은 좋다,,
확실히 코드는 처음에는 이해가 안돼도 천천히 읽어보고 다시 만들어보면 뇌에 스며들게 되는 것 같다.. 물론 안 보고 똑같이는 아직 못 만들겠지만.. (사실 이것도 과제로 내주면 검색해 가면서 어떻게든 만들겠지..)
'프론트엔드 정복기 > 리액트' 카테고리의 다른 글
[React]16 성능최적화2 (useCallback) (0) | 2023.01.16 |
---|---|
[React]15 리액트 성능 최적화(useMemo) (0) | 2023.01.12 |
[React]13 (node-Sass,styled-component) (0) | 2023.01.12 |
[React]12 Hooks (useState,useEffect,useRef,useContext) (0) | 2023.01.11 |
[React] 11 Hooks( useEffect,useRef ) (0) | 2023.01.11 |