[DevCamp] ๐Ÿงฉ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์Šคํƒ€์ผ๋“œ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑํ•ด๋ณด๊ธฐ

๋™๊ฑดยท2025๋…„ 4์›” 18์ผ

DevCamp

๋ชฉ๋ก ๋ณด๊ธฐ
51/85

๐Ÿงฉ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์Šคํƒ€์ผ๋“œ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑํ•ด๋ณด๊ธฐ

์˜ค๋Š˜์€ ์‹ค์ œ styled-components๋ฅผ ํ™œ์šฉํ•ด
Button, Title, InputText ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•œ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด

์–ด๋–ป๊ฒŒ ํƒ€์ž…๊ณผ ํ…Œ๋งˆ๋ฅผ ํ™œ์šฉํ•ด์„œ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์ข‹์€ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์ •๋ฆฌํ–ˆ๋‹ค.


๐ŸŽฏ ๋ชฉํ‘œ

  • UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ๊ตฌ์„ฑํ•˜๊ธฐ
  • theme๊ณผ styled-components๋ฅผ ํ™œ์šฉํ•ด ์ผ๊ด€๋œ ์Šคํƒ€์ผ ์ ์šฉ
  • ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ

๐Ÿ–ฒ๏ธ Button ์ปดํฌ๋„ŒํŠธ

interface Props {
  children: React.ReactNode;
  size: ButtonSize;
  scheme: ButtonScheme;
  disabled?: boolean;
  isLoading?: boolean;
}

์ฃผ์š” ํฌ์ธํŠธ

  • ๋ฒ„ํŠผ์˜ ์Šคํƒ€์ผ ์Šคํ‚ด(scheme) ๊ณผ ํฌ๊ธฐ(size) ๋ฅผ props๋กœ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค.
  • disabled๋‚˜ isLoading์ด ์ ์šฉ๋˜๋ฉด ํด๋ฆญ์„ ๋ง‰๊ณ  ์Šคํƒ€์ผ๋„ ์กฐ์ ˆ๋จ.
  • theme.button[size].fontSize, theme.buttonScheme[scheme] ๋“ฑ ํ…Œ๋งˆ๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ ์Šคํƒ€์ผ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
const ButtonStyle = styled.button<Omit<Props, "children">>`
  font-size: ${({ theme, size }) => theme.button[size].fontSize};
  padding: ${({ theme, size }) => theme.button[size].padding};
  ...
`

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ๋งˆ ์„ค์ •๋งŒ ๋ฐ”๊ฟ”๋„ ์ „์ฒด ํ”„๋กœ์ ํŠธ ๋ฒ„ํŠผ ์Šคํƒ€์ผ์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์–ด์„œ ์œ ์ง€๋ณด์ˆ˜์— ๋งค์šฐ ์œ ๋ฆฌํ•˜๋‹ค.


๐Ÿ“ Title ์ปดํฌ๋„ŒํŠธ

interface Props {
  children: React.ReactNode;
  size: "large" | "medium" | "small";
  color?: ColorKey;
}

์ฃผ์š” ํฌ์ธํŠธ

  • ์ œ๋ชฉ ์ปดํฌ๋„ŒํŠธ๋„ theme์„ ํ†ตํ•ด ํฐํŠธ ํฌ๊ธฐ์™€ ์ƒ‰์ƒ์„ ๋ฐ›์•„์˜จ๋‹ค.
  • color๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ์ƒ‰์ƒ(primary)์„ ์‚ฌ์šฉํ•œ๋‹ค.
const TitleStyle = styled.h1<Omit<Props, 'children'>>`
  font-size: ${({ theme, size }) => theme.heading[size].fontSize};
  color: ${({ theme, color }) => color ? theme.color[color] : theme.color.primary};
`;

์ด์ฒ˜๋Ÿผ ์„ ํƒ์ ์œผ๋กœ ์Šคํƒ€์ผ ์†์„ฑ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ฉด ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์—์„œ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์•„์ง„๋‹ค.


๐Ÿ”ค InputText ์ปดํฌ๋„ŒํŠธ

interface Props {
  placeholder?: string;
}

์ฃผ์š” ํฌ์ธํŠธ

  • forwardRef๋ฅผ ํ†ตํ•ด ์™ธ๋ถ€์—์„œ input์— ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ๋จ.
  • styled.input.attrs๋กœ input ํƒ€์ž…์„ ๊ณ ์ •์‹œํ‚ค๋ฉด์„œ๋„ ์Šคํƒ€์ผ์€ theme ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌ.
const InputTextStyled = styled.input.attrs({ type: "text" })`
  padding: 0.25rem 0.75rem;
  border: 1px solid ${({ theme }) => theme.color.border};
  ...
`;

์ด ํŒจํ„ด์€ ํผ ์š”์†Œ๋ฅผ ๋งŒ๋“ค ๋•Œ ์ž์ฃผ ์“ฐ์ด๋Š” ํ˜•ํƒœ๋‹ค. ํŠนํžˆ forwardRef๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋‚˜ ํฌ์ปค์Šค ์ œ์–ด ๋“ฑ์—์„œ ์œ ์šฉํ•˜๋‹ค.


๐Ÿ’ก ์ „์ฒด์ ์œผ๋กœ ๋А๋‚€ ์ 

์ด๋Ÿฌํ•œ ๊ตฌ์„ฑ์€ "๋””์ž์ธ ์‹œ์Šคํ…œ"์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ ๊ธฐ๋ฐ˜์„ ์ž˜ ์žก์•„์ฃผ๋Š” ํŒจํ„ด์ด๋‹ค.
ํŠนํžˆ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์ด ์žˆ๋‹ค:

  • ํ…Œ๋งˆ ๊ธฐ๋ฐ˜์œผ๋กœ ์Šคํƒ€์ผ์„ ํ†ตํ•ฉ ๊ด€๋ฆฌ
  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์บก์Аํ™”๋˜์–ด ์ถ”์ ๊ณผ ์žฌ์‚ฌ์šฉ์ด ์‰ฌ์›€
  • props๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ UI ๋ณ€ํ˜•์„ ์‰ฝ๊ฒŒ ์ œ๊ณต

๐Ÿ”จ TIL

  • styled-components์™€ TypeScript์˜ ์กฐํ•ฉ์€ ๊ฐ•๋ ฅํ•˜๋‹ค. ํƒ€์ž… ๊ธฐ๋ฐ˜์˜ ์Šคํƒ€์ผ ๊ตฌ์„ฑ์œผ๋กœ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.
  • theme์„ ์ ๊ทน์ ์œผ๋กœ ํ™œ์šฉํ•˜๋ฉด ์Šคํƒ€์ผ ๋ณ€๊ฒฝ์ด ๊ฐ„ํŽธํ•˜๋‹ค.
  • Omit<Props, "children">์™€ ๊ฐ™์ด ํƒ€์ž…์„ ์œ ์—ฐํ•˜๊ฒŒ ํ™œ์šฉํ•˜๋ฉด styled-components์— ์ •ํ™•ํ•œ ํƒ€์ž…์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • forwardRef๋Š” ํผ ๊ตฌ์„ฑ ์‹œ ๊ผญ ์ตํ˜€์•ผ ํ•  ํŒจํ„ด์ด๋‹ค.
profile
๋ฐฐ๊ณ ํ”ˆ ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€