Custom Component ๐ŸŒธ

forhreverยท2023๋…„ 2์›” 20์ผ
0

CDD (Component Driven Development)

์ปดํฌ๋„ŒํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(CDD)์€ ์ปดํฌ๋„ŒํŠธ ์ฃผ์œ„์— ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค๋ฅผ ๊ณ ์ •ํ•˜๋Š” ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก ์ด๋‹ค.
์ปดํฌ๋„ŒํŠธ ์ˆ˜์ค€์—์„œ ์‹œ์ž‘ํ•ด ํŽ˜์ด์ง€๋‚˜ ํ™”๋ฉด ์ˆ˜์ค€์—์„œ ๋๋‚˜๋Š” ๊ฒƒ์œผ๋กœ "๋ฐ”๋‹ฅ๋ถ€ํ„ฐ ๋๊นŒ์ง€", UI๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ณผ์ •์„ ๋งํ•œ๋‹ค.
๋ ˆ๊ณ ์ฒ˜๋Ÿผ ์กฐ๋ฆฝํ•ด ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๋ถ€ํ’ˆ ๋‹จ์œ„๋กœ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋‚˜๊ฐ€๋Š” ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.


๐ŸŽจ Styled Components

css ์ปดํฌ๋„ŒํŠธํ™” ์‹œํ‚ด์œผ๋กœ์จ ํ•ด๊ฒฐํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ๋‹ค.
React ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Styled Components ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.

css ์ฝ”๋“œ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ์˜ ๋ถˆํŽธํ•จ

  • class, id ์ด๋ฆ„์„ ์ง“๋Š๋ผ ๊ณ ๋ฏผํ•œ ์  ์žˆ๋‹ค.
  • css ํŒŒ์ผ ์•ˆ์—์„œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋ถ€๋ถ„์„ ์ฐพ๊ธฐ ํž˜๋“ค๋‹ค.
  • css ํŒŒ์ผ์ด ๋„ˆ๋ฌด ๊ธธ์–ด์ ธ์„œ ํŒŒ์ผ์„ ์ชผ๊ฐœ์„œ ๊ด€๋ฆฌํ•œ ์ ์ด ์žˆ๋‹ค.
  • ์Šคํƒ€์ผ ์†์„ฑ์ด ๊ฒน์ณ์„œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค์ง€ ์•Š์€ ์ ์ด ์žˆ๋‹ค.

CSS in JS

css๋„ ์‰ฝ๊ฒŒ Javascript ์•ˆ์— ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, HTML + JS + CSS๊นŒ์ง€ ๋ฌถ์–ด์„œ ํ•˜๋‚˜์˜ JSํŒŒ์ผ ์•ˆ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋‹ค.

Styled Components ์„ค์น˜ ๋ฐฉ๋ฒ•

npm install --save styled-components

Styled Components๋Š” package.json์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋„๋ก ๊ถŒ์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

{
  "resolutions": {
    "styled-components": "^5"
  }
}

1. Styled Components๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•

const ์ปดํฌ๋„ŒํŠธ์ด๋ฆ„ = styled.ํƒœ๊ทธ์ข…๋ฅ˜`
   css์†์„ฑ 1 : ์†์„ฑ๊ฐ’;
   css์†์„ฑ 2 : ์†์„ฑ๊ฐ’;
`;

์˜ˆ)

const BlueButton = styled.button`
  background-color : blue;
  color : white;
`;

2. ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌํ™œ์šฉํ•ด์„œ ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ

const ์ปดํฌ๋„ŒํŠธ์ด๋ฆ„ = styled(์žฌํ™œ์šฉํ•  ์ปดํฌ๋„ŒํŠธ)`
  ์ถ”๊ฐ€ํ•  css ์†์„ฑ 1 : ์†์„ฑ๊ฐ’;
  ์ถ”๊ฐ€ํ•  css ์†์„ฑ 2 : ์†์„ฑ๊ฐ’;
`;

์˜ˆ)

const BigBlueButton = styled(BlueButton)`
  padding : 10px;
  margin-top : 10px;
`;

3. Props ํ™œ์šฉํ•˜๊ธฐ

const ์ปดํฌ๋„ŒํŠธ์ด๋ฆ„ = styled.ํƒœ๊ทธ์ข…๋ฅ˜`
  css ์†์„ฑ : ${ (props) => ํ•จ์ˆ˜ ์ฝ”๋“œ }
  `;

1) Props๋กœ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋งํ•˜๊ธฐ

const Button = styled.button`
  background: ${ (props) => props.skyblue ? "skyblue" : "white" }
`;
<Button skyblue>Button1</Button>
<Button>Button2</Button>

2) Props ๊ฐ’์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ

const Button1 = styled.button`
  background: ${ (props) => props.color ? "props.color" : "white" }
`;
<Button1>Button1</Button>
<Button1 color = "orange">Button2</Button1>
<Button1 color = "tomato">Button3</Button1>
const Button2 = styled.button`
  background: ${(props) => props.color || "white"};
`;
<Button2>Button1</Button2>
<Button2 color="pink">Button2</Button2>
<Button2 color="turquoise">Button3</Button2>

4. ์ „์—ญ ์Šคํƒ€์ผ ์„ค์ •ํ•˜๊ธฐ

import { createGlobalStyle } from "styled-components";
const GlobalStyle = createGlobalStyle`
	button {
		padding : 5px;
    margin : 2px;
    border-radius : 5px;
	}
`
function App() {
	return (
		<>
			<GlobalStyle />
			<Button>์ „์—ญ ์Šคํƒ€์ผ ์ ์šฉํ•˜๊ธฐ</Button>
		</>
	);
}

๐Ÿ“ข Storybook

๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๋”ฐ๋กœ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌ์„ฑํ•ด ์ฃผ์–ด ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ๊ฐœ๋ฐœ ์Šคํƒ์„ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๊ฐ•์ œ ์ด๋™ํ•˜๊ฑฐ๋‚˜, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํƒ์ƒ‰ํ•  ํ•„์š” ์—†์ด ์ „์ฒด UI๋ฅผ ํ•œ๋ˆˆ์— ๋ณด๊ณ  ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์žฌ์‚ฌ์šฉ์„ฑ์„ ํ™•๋Œ€ํ•˜๊ธฐ ์œ„ํ•ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฌธ์„œํ™”ํ•œ๋‹ค.
  • ์ž๋™์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹œ๊ฐํ™”ํ•˜์—ฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ํ…Œ์ŠคํŠธ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฒ„๊ทธ๋ฅผ ์‚ฌ์ „์— ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋ฐ ๊ฐœ๋ฐœ ์†๋„๋ฅผ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ์žฅ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋˜ํ•œ ์˜์กด์„ฑ์„ ๊ฑฑ์ •ํ•˜์ง€ ์•Š๊ณ  ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค.

Storybook์—์„œ ์ง€์›ํ•˜๋Š” ์ฃผ์š” ๊ธฐ๋Šฅ

  • UI ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์นดํƒˆ๋กœ๊ทธํ™”ํ•˜๊ธฐ
  • ์ปดํฌ๋„ŒํŠธ ๋ณ€ํ™”๋ฅผ stories๋กœ ์ €์žฅํ•˜๊ธฐ
  • ํ•ซ ๋ชจ๋“ˆ ์žฌ ๋กœ๋”ฉ๊ณผ ๊ฐ™์€ ๊ฐœ๋ฐœ ํˆด ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ธฐ
  • ๋ฆฌ์•กํŠธ๋ฅผ ํฌํ•จํ•œ ๋‹ค์–‘ํ•œ ๋ทฐ ๋ ˆ์ด์–ด ์ง€์›ํ•˜๊ธฐ

Storybook์„ ์„ค์น˜๋ฐฉ๋ฒ•

npx storybook init

Storybook ์‹คํ–‰ํ•˜๊ธฐ

npm run storybook

๐Ÿ”— useRef

useRef ๋กœ DOM ๋…ธ๋“œ, ์—˜๋ฆฌ๋จผํŠธ, ๊ทธ๋ฆฌ๊ณ  React ์ปดํฌ๋„ŒํŠธ ์ฃผ์†Œ๊ฐ’์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

const ์ฃผ์†Œ๊ฐ’์„_๋‹ด๋Š”_๊ทธ๋ฆ‡ = useRef(์ฐธ์กฐ์ž๋ฃŒํ˜•)
return (
    <div>
      <input ref={์ฃผ์†Œ๊ฐ’์„_๋‹ด๋Š”_๊ทธ๋ฆ‡} type="text" />
    </div>);

์˜ˆ1) focus

import React, { useRef } from "react";

const Focus = () => {
  const f1fo = useRef(null);
  const s2fo = useRef(null);
  const t3fo = useRef(null);

  const handleInput = (event) => {
    console.log(event.key, event);
    if (event.key === "Enter") {
      if (event.target === f1fo.current) {
        s2fo.current.focus();
        event.target.value = "";
      } else if (event.target === s2fo.current) {
        t3fo.current.focus();
        event.target.value = "";
      } else if (event.target === t3fo.current) {
        f1fo.current.focus();
        event.target.value = "";
      } else {
        return;
      }
    }
  };

  return (
    <div>
      <h1>ํƒ€์ž์—ฐ์Šต</h1>
      <h3>๊ฐ ๋‹จ์–ด๋ฅผ ๋ฐ”๋ฅด๊ฒŒ ์ž…๋ ฅํ•˜๊ณ  ์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด์„ธ์š”.</h3>
      <div>
        <label>hello </label>
        <input ref={f1fo} onKeyUp={handleInput} />
      </div>
      <div>
        <label>world </label>
        <input ref={s2fo} onKeyUp={handleInput} />
      </div>
      <div>
        <label>codestates </label>
        <input ref={t3fo} onKeyUp={handleInput} />
      </div>
    </div>
  );
};

export default Focus;

์˜ˆ2) media playback

import { useRef } from "react";
function App() {
  const videoRef = useRef(null);

  const playVideo = () => {
    videoRef.current.play();
    console.log(videoRef.current);
  };

  const pauseVideo = () => {
    videoRef.current.pause();
    videoRef.current.remove();
  };

  return (
    <div className="App">
      <div>
        <button onClick={playVideo}>Play</button>
        <button onClick={pauseVideo}>Pause</button>
      </div>
      <video ref={videoRef} width="320" height="240" controls>
        <source
          type="video/mp4"
          src="https://player.vimeo.com/external/544643152.sd.mp4?s=7dbf132a4774254dde51f4f9baabbd92f6941282&profile_id=165"
        />
      </video>
    </div>
  );
}

export default App();


์˜ค๋Š˜ ํ•™์Šต์„ ๋งˆ์น˜๊ณ  ๋‚˜์„œ, ๐Ÿ“–

styled components์™€ useRef๋Š” ์ง์ ‘ vscode๋กœ ์ฝ”๋“œ๋ฅผ ์ณ๋ณด๋ฉด์„œ ์‹ค์Šตํ•ด ๋ณด์•˜์ง€๋งŒ, Storybook์€ ์‹คํ–‰์ด ์•ˆ ๋˜๋Š” ํ˜„์ƒ์ด ๊ณ„์† ์ง€์†๋˜์–ด ์ถ”ํ›„์— ํ•ด๋ณผ ์˜ˆ์ •์ด๋‹ค.
์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘์„ฑ๊ณผ ํšจ์œจ์„ฑ์„ ๋‹ค์‹œ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์—ˆ๊ณ , css ํŒŒ์ผ์„ ํ†ตํ•˜์ง€ ์•Š์•„๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ฒŒ ๋˜์–ด ์ƒˆ๋กœ์› ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋Š” ํ•ญ์ƒ ์–ด๋ ต๋‹ค.. ๋” ์ง‘์ค‘ํ•˜์—ฌ ์ž๋ฃŒ๋ฅผ ๋” ์ฐพ์•„๋ณด๋ฉด์„œ ์ดํ•ดํ•˜๋„๋ก ๋…ธ๋ ฅํ•ด์•ผ๊ฒ ๋‹ค.

profile
๊ฐœ๋ฐœ์ž ์„ฑ์žฅ ๊ณ„๋‹จ ์˜ฌ๋ผ๊ฐ€๊ธฐ

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