본문 바로가기
React

[컴포넌트 재활용하기] - Button

by 도현위키 2022. 2. 19.

버튼 컴포넌트 재활용하기

 

오늘은 재활용 가능한 컴포넌트 중에 가장 많이 사용되는 Button을 만들어 보자.

 

개발 환경

- react 17.0.2
- typescript 4.4.2
- styled-component 5.3.3

 

1. styled-components를 적용한 기본적인 버튼 컴포넌트 생성

src/components/Button.tsx 

import React, { ReactElement } from "react";
import styled from "styled-components";

function Button(): ReactElement {
  return <ButtonStyled>Button</ButtonStyled>;
}

const ButtonStyled = styled.button``;

export default Button;

 

우리는 여기서 고민을 해야 한다. 버튼을 어느정도까지 외부에서 커스텀 가능하게 해야 할까??

여기서부터는 각 프로젝트의 디자인이 어떻게 나왔냐에 따라서 달라진다. 하지만 내가 만들려고 하는 재활용 가능한 버튼은 디자인과 관계없이 최대한 재활용이 가능하게 만들려고 한다. 따라서 아래의 값들은 꼭 입력받았으면 좋겠다고 생각한다. (지극히 주관적임, 상황에 맞게 속성을 추가하거나 빼도됌)

 

  • width
  • height
  • buttonColor
  • hasBorder
  • borderColor
  • borderRadius
  • fontColor
  • fontSize

 

여기서 나는 width, height의 값을 string 타입으로 받을지, number 타입으로 받을지 고민이 됐다. 고민이 됐던 이유는 첫 번째로 string으로 받게 되면 width="150px" 이런 식으로 받을텐데, 만약 개발하다 실수로 width="150pc" 이렇게 입력해도 따로 오류가 나타나지 않는다. 그래서 왜 안되지? 하고 개발자도구로 한번 더 체크해야하는 일이 생길 수 있다. 

 

그렇다면 number 타입으로 받으면 되지 않는가? 할 수 있는데 number로 받게되면 무조건 px단위로 버튼을 만들어야 하는 불편함이 생긴다. 이게 무슨 말이냐면 string 타입으로 받게되면 width="100%"   height="auto" 같은걸 다 사용할 수 있는데, number로 하게 되면 width={100}  height={30} 이런식으로 밖에 하지 못한다.

 

고민 끝에 나는 string으로 받으려고 한다. 그 이유는 number 타입으로 받게 되면 px로 한정돼서 특정한 상황에 사용하지 못하는 경우가 생길 수 있다. 그러나 string으로 하게 되면 실수는 있을 수 있지만, 모든 단위의 사이즈를 다룰 수 있기 때문에 나는 string으로 결정했다.

 

2. 프로젝트에 사용될 컬러 세팅

우선 버튼 컴포넌트를 구현하기 전에 우리 프로젝트에서 사용될 컬러를 만들어 놓자. 컬러를 만들어 놓는 이유는 아래에서 다시 작성하겠다.

 

src/styles/palette.ts

export const palette = {
  red: "#FF0000",
  orange: "#FFA500",
  yellow: "#FFFF00",
  green: "#008000",
  blue: "#0000FF",
  indigo: "#4B0082",
  violet: "#EE82EE",
  white: "#FFFFFF",
  black: "#000000",
};

export type PaletteKeyTypes = keyof typeof palette;

 

타입까지 만들어 놓는 이유는 이따가 버튼 컴포넌트에서 사용할 예정이다.

 

3. 버튼 컴포넌트에 style Props 지정

위에서 입력받기로 했던 props들을 Button 컴포넌트에 작성해보자.

import React, { ReactElement } from "react";
import styled, { css } from "styled-components";
import { PaletteKeyTypes } from "../styles/palette";

interface ButtonStyle {
  width?: string;
  height?: string;
  buttonColor?: PaletteKeyTypes;
  hasBorder?: boolean;
  borderColor?: PaletteKeyTypes;
  borderRadius?: string;
  fontColor?: PaletteKeyTypes;
  fontSize?: string;
}

function Button({ ...rest }: ButtonStyle): ReactElement {
  return <ButtonStyled {...rest}>Button</ButtonStyled>;
}

const ButtonStyled = styled.button<ButtonStyle>`
  cursor: pointer;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  ${({
    width = "auto",
    height = "auto",
    buttonColor = "white",
    hasBorder = false,
    borderColor = "white",
    borderRadius = "4px",
    fontColor = "black",
    fontSize = "14px",
  }) => css`
    width: ${width};
    height: ${height};
    background-color: ${palette[buttonColor]};
    border: ${hasBorder ? `1px solid ${palette[borderColor]}` : "none"};
    border-radius: ${borderRadius};
    color: ${palette[fontColor]};
    font-size: ${fontSize};
  `}
`;

export default Button;

 

props를 optional 문법으로 받은 후에 아래에서 default 값을 설정해서 아무것도 입력받지 않아도 기본 값이 나오도록 구현했다.  커스텀 하고 싶은 부분만 props로 넣어주면 된다. 

 

여기서 interface 안에 Color 타입들을 PaletteKeyTypes로 지정한 이유는 우리가 프로젝트에 사용하기로 한 palette들의 key값들을 아래 이미지와 같이 자동완성으로 보여주기 위해서다.

자동완성

 

 

4. children, className 받기

우리는 이제 버튼에 들어갈 텍스트(+아이콘)과 어디서든 원하는 데로 커스텀이 가능하도록 className을 받도록 설정해야 한다. props를 아래 같이 바꿔주자.

 

import React, { ReactElement, ReactNode } from "react";
import styled, { css } from "styled-components";
import { PaletteKeyTypes } from "../styles/palette";

interface ButtonStyle {
  width?: string;
  height?: string;
  buttonColor?: PaletteKeyTypes;
  hasBorder?: boolean;
  borderColor?: PaletteKeyTypes;
  borderRadius?: string;
  fontColor?: PaletteKeyTypes;
  fontSize?: string;
}

interface ButtonProps extends ButtonStyle {
  children: ReactNode;
  className?: string;
}

function Button({ className, children, ...rest }: ButtonProps): ReactElement {
  return (
    <ButtonStyled className={className} {...rest}>
      {children}
    </ButtonStyled>
  );
}

... 생략

 

interface를 Style과 Props를 분리한 이유는 Style 관련 타입은 아래의 ButtonStyled에 Style 관련된 타입만 주기 위해서다.  

 

이렇게 하면 버튼의 모든 기능이 정상적을 작동한다. 그러나 아직도 좀 불편한 것이 있다.

우선 기존 html에서 제공하는 버튼을 한번 보자.

 

이런 식으로 'onCl'만 입력해도 그와 관련된 이벤트 함수들이 자동완성으로 다 나온다. 우리가 만든 버튼은 어떻게 나오나 한번 해보자.

 

 

"제안 항목이 없습니다."라고 나온다. 

 

5. Button 타입 설정

우리가 Button의 타입을 지정해주지 않아서 자동완성이 되지 않는 거다. 다시 버튼 컴포넌트로 돌아와서 interface를 다음과 같이 수정해보자.

 

interface ButtonStyle {
  width?: string;
  height?: string;
  buttonColor?: PaletteKeyTypes;
  hasBorder?: boolean;
  borderColor?: PaletteKeyTypes;
  borderRadius?: string;
  fontColor?: PaletteKeyTypes;
  fontSize?: string;
}

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    ButtonStyle {
  children: ReactNode;
  className?: string;
}


... 생략

 

 

extends 부분에 버튼 타입을 추가해줬다. 그럼 다시 자동완성이 되는지 확인해보자.

 

 

 

아주 잘된다...!   

 

 

최종 코드

import React, { ReactElement, ReactNode } from "react";
import styled, { css } from "styled-components";
import { palette, PaletteKeyTypes } from "../styles/palette";

interface ButtonStyle {
  width?: string;
  height?: string;
  buttonColor?: PaletteKeyTypes;
  hasBorder?: boolean;
  borderColor?: PaletteKeyTypes;
  borderRadius?: string;
  fontColor?: PaletteKeyTypes;
  fontSize?: string;
}

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    ButtonStyle {
  children: ReactNode;
  className?: string;
}

function Button({ className, children, ...rest }: ButtonProps): ReactElement {
  return (
    <ButtonStyled className={className} {...rest}>
      {children}
    </ButtonStyled>
  );
}

const ButtonStyled = styled.button<ButtonStyle>`
  cursor: pointer;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  ${({
    width = "auto",
    height = "auto",
    buttonColor = "white",
    hasBorder = false,
    borderColor = "white",
    borderRadius = "4px",
    fontColor = "black",
    fontSize = "14px",
  }) => css`
    width: ${width};
    height: ${height};
    background-color: ${palette[buttonColor]};
    border: ${hasBorder ? `1px solid ${palette[borderColor]}` : "none"};
    border-radius: ${borderRadius};
    color: ${palette[fontColor]};
    font-size: ${fontSize};
  `}
`;

export default Button;

 

 

사용은 아래 처럼 하면 된다.

import React from "react";
import Button from "./components/Button";

function App() {
  return (
    <Button
      width="150px"
      height="30px"
      buttonColor="green"
      fontColor="indigo"
      fontSize="16px"
    >
      Button
    </Button>
  );
}

export default App;

 

 

 

마치며

이렇게 오늘은 재활용 가능한 버튼 컴포넌트를 만들어 보았다. 

모든 프로젝트에서 아마 가장 많이 사용되는 컴포넌트가 버튼이 아닐까 생각한다.  오늘 구현한 버튼은 재활용성이 너무 높아서 props를 많이 받았지만, 아예 내부에서 색상을 지정해놓고 외부에서 불러오기만 하면 색상이 나오도록 하는 방식도 있다. 둘 중에 프로젝트에 맞게끔 알아서 선택하면 좋을 거 같다!

댓글