lil.dev
Published on

๐ŸŽ‰ ์ปฌ๋Ÿฌ๋ฆฌ์ŠคํŠธ ํ”„๋กœ์ ํŠธ #2 ์ปฌ๋Ÿฌ ์„ ํƒ ๊ธฐ๋Šฅ ๊ตฌํ˜„

๊ธ€์“ด์ด

    ๐Ÿ“Œ ๋ชฉ์ฐจ

    Welcome

    โœจ ์ปฌ๋Ÿฌ๋ฆฌ์ŠคํŠธ ์‚ฌ์ดํŠธ(์•„์ง ์—†์Œ

    ๐Ÿ’๐Ÿป

    1. ๊ฐœ๋ฐœ ๋ฐฉ์‹
    2. ์ฝ”๋“œ ์งœ๊ธฐ ์ „ ์ค€๋น„
    3. ์ฝ”๋“œ ์งœ๊ธฐ ์‹œ์ž‘
    • 3.1 ์„ ํƒํ•œ ์นธ ์ƒ‰๊น” ์ถ”๊ฐ€ ๐Ÿฅš
    • 3.2 ์„ ํƒํ•œ ์นธ ์ƒ‰๊น” ์‚ญ์ œ(๋‹ค์‹œ ํ™”์ดํŠธ๋กœ) ๐Ÿฅš
    • 3.3 ์„ ํƒํ•œ ์นธ ์ƒ‰๊น” ์žฌ์„ ํƒ - ๋นˆ ์นธ ์ˆœ์„œ๋Œ€๋กœ ๐Ÿฅš

    1. ๊ฐœ๋ฐœ ๋ฐฉ์‹

    BDD(Behavior Driven Development) ๐Ÿ„

    feat.ํƒ์ •ํ† ๋ผ๋‹˜

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

    • ์–ด๋–ค ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ์ง€ ๊ธฐ์–ตํ•˜๊ธฐ ์‰ฝ๊ณ ,
    • ์ •ํ™•ํžˆ ์–ด๋–ค ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ์ง€ ์ข€ ๋” ๋ช…ํ™•ํžˆ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ ,
    • ๊ตฌํ˜„ ์ˆœ์„œ๋ฅผ ์•ก์…˜ ๋ณ„๋กœ ๋‚˜๋ˆ„์–ด ์ •๋ฆฌํ•˜๊ธฐ ์ข‹๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

    ๋˜ ์ดˆ๋ณด์ธ ๋‚ด๊ฐ€ ๋ณต์Šตํ•˜๊ธฐ์—๋„ ์ด ๋ฐฉ์‹์ด ์ฒด๊ณ„์ ์œผ๋กœ ๊ธฐ๋ก์„ ๋ณผ ์ˆ˜ ์žˆ์–ด ์ฐธ ์ข‹์•˜๋‹ค.


    2. ์ฝ”๋“œ ์งœ๊ธฐ ์ „ ์ค€๋น„

    1. BDD(Behavior Driven Development)๊ฐœ๋ฐœ์„ ํ•˜๊ธฐ์œ„ํ•ด featureํŒŒ์ผ์„ ๋งŒ๋“ค์—ˆ๋‹ค. -> 'App.feture'ํŒŒ์ผ ์ƒ์„ฑ
    2. featureํŒŒ์ผ์— ์ฃผ์„์„ ๋‹ฌ๊ธฐ ์œ„ํ•ด Cucumber (Gherkin) Full Support ์ต์Šคํ…์…˜์„ ์„ค์น˜ํ–ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ์ด๋ฏธ์ง€์˜ ์ต์Šคํ…์…˜์„ ์„ค์น˜ํ•˜๋ฉด ๋œ๋‹ค!
    3. bun์œผ๋กœ create-react-app์„ ์‹คํ–‰ํ•ด ํ”„๋กœ์ ํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ–ˆ๋‹ค. bun create react ํ”„๋กœ์ ํŠธ์ด๋ฆ„
    4. index.html์˜ head์— pico.css๋ฅผ ์ถ”๊ฐ€ pico.css : classless css์ž„! ๊ธฐ๋ณธ html์„ ์˜ˆ์˜๊ฒŒ ๋งŒ๋“ค์–ด์ค€๋‹ค.
    <link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css" />
    

    3. ์ฝ”๋“œ ์งœ๊ธฐ ์‹œ์ž‘

    App.tsx ์ž‘์„ฑ์„ ์‹œ์ž‘ํ–ˆ๋‹ค.

    ๊ธฐ๋Šฅ 0. ์ƒ‰์ƒ ํŒŒ๋ ˆํŠธ ๋งŒ๋“ค๊ธฐ

    ์ƒ‰์ƒ ์ฝ”๋“œ๋ฅผ hex์ฝ”๋“œ๋กœ ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด map์œผ๋กœ ๋ฒ„ํŠผ์— ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

    const WHITE_HEX = '#ffffff'
    const pallete = [
      '#e6e6e6',
      '#d2b48c',
      '#800000',
      '#7e181e',
      '#29f9ff',
      '#a8a8f8',
      '#5050f1',
      '#ffb3ba',
      '#ccff00',
      '#ff7f50',
      '#fa8072',
      '#bada55',
    ]
    
    ;<div id="pallete" className="flex flex-row flex-wrap">
      {/* 4๋ฒˆ - pallete๋„ ๊ฐ’์ด ์—ฌ๋Ÿฌ ๊ฐœ์ด๋ฏ€๋กœ map์„ ์‚ฌ์šฉํ•œ๋‹ค. */}
      {pallete.map((hex) => (
        <button
          onClick={() => addSelected(hex)}
          className="roundButton"
          style={{ backgroundColor: hex }}
        >
          {hex}
        </button>
      ))}
    </div>
    

    ๊ธฐ๋Šฅ 1. ์„ ํƒํ•œ ์นธ ์ƒ‰๊น” ์ถ”๊ฐ€ ๐Ÿฅš

    ์‹œ๋‚˜๋ฆฌ์˜ค

    Scenario: ์„ ํƒ๋œ ์ƒ‰๊น” ๋ชฉ๋ก์ด ๋ฐ•์Šค์— ๋ณด์ธ๋‹ค
    
    # Given - before, ์ฃผ์–ด์ง„ ์ƒํ™ฉ
    
    Given ์„ ํƒ๋œ ์ƒ‰๊น”๋“ค๋กœ ๋ฌธ์ œ๋ฅผ ๋ Œ๋”ํ•˜๊ณ 
    
    # When - ์‚ฌ์šฉ์ž์˜ ๋™์ž‘ (ํด๋ฆญ, ํ‚ค๋ณด๋“œ, ์Šคํฌ๋กค, ํƒญ, ์ง€์šฐ๊ธฐ, ๋ณต๋ถ™)
    
    # Then - after, ๊ฒฐ๊ณผ
    
    Then ์„ ํƒ๋œ ์ƒ‰๊น” ๋ชฉ๋ก์ด ๋ณด์ธ๋‹ค
    

    ๋จผ์ € ์ž„์˜์˜ ์„ ํƒ๋œ ์ƒ‰์ƒ ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.

    const colors = ['red', 'green', 'blue']
    

    ๊ฐ’์ด ์—ฌ๋Ÿฌ ๊ฐœ์ธ ๋ฐฐ์—ด์€ map์„ ์จ์„œ element ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค..!

    {
      colors.map((color) => (
        <td
          style={{
            backgroundColor: color,
          }}
        >
          {color}
        </td>
      ))
    }
    

    ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฐ’์„ background์— ๋ฌธ์ž์—ด๋กœ ๋„ฃ์–ด์ฃผ์–ด ์นธ์— ํ•ด๋‹นํ•˜๋Š” ์ƒ‰์ด ๋ณด์ด๋„๋ก ํ•œ๋‹ค.

    <td
        style={{
                backgroundColor: color,
              }}
     >
    
    • ์„ ํƒํ•œ ์ƒ‰๊น” ๋ณด์—ฌ์ฃผ๊ธฐ ๊ธฐ๋Šฅ
    function App() {
      //1๋ฒˆ- ์„ ํƒ๋œ ์ƒ‰์ด ์—ฌ๋Ÿฌ๊ฐœ์ธ ๋ฐฐ์—ด์„ ๋งŒ๋“ ๋‹ค
      const colors = ['red', 'green', 'blue']
      return (
        <div className="App">
          <article>
            <table className="selectedContainer flex flex-row">
              <tr>
                {/* 2๋ฒˆ - map์„ ์จ์„œ ๋ฐฐ์—ด์˜ ๊ฐ’์„ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ค€๋‹ค. */}
                {colors.map((color) => (
                  <td
                    style={{
                      backgroundColor: color,
                    }}
                  >
                    {color}
                  </td>
                ))}
              </tr>
            </table>
            <div id="pallete" className="flex flex-row flex-wrap"></div>
          </article>
        </div>
      )
    }
    
    • ์ƒ‰๊น” ์„ ํƒํ•˜๊ธฐ ๊ธฐ๋Šฅ
    function App() {
      //์„ ํƒ๋œ ์ƒ‰์ด ์—ฌ๋Ÿฌ๊ฐœ์ธ ๋ฐฐ์—ด์„ ๋งŒ๋“ ๋‹ค - BOX_COUNT ๊ฐœ์ธ ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด์„œ ํฐ์ƒ‰์œผ๋กœ ์ฑ„์šด๋‹ค
      const [colors, setColors] = React.useState(Array(BOX_COUNT).fill(WHITE_HEX)) //๋ฌธ์ž์—ด
      //addSelected ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด
      function addSelected(hex: string) {
        setColors((old) => {
          // findIndex๋Š” ๋ฐฐ์—ด์˜ ๊ฐ’ ์ค‘์—์„œ ์กฐ๊ฑด์— ๋งž๋Š” ๊ฐ’์ด ์žˆ๋Š” ์ฒซ ๋ฒˆ์งธ index๋ฅผ ์ฐพ๋Š”๋‹ค!
          // hex ์ค‘์—์„œ hex์˜ ์ƒ‰์ด white์ธ ์ฒซ๋ฒˆ์งธ index๋ฅผ ์ฐพ์•„๋‚ธ๋‹ค.
          const firstWhiteIndex = old.findIndex((hex) => hex === WHITE_HEX)
          if (firstWhiteIndex === -1) {
            // ์ธ๋ฑ์Šค๋ฅผ ๋ชป ์ฐพ์œผ๋ฉด! -1์„ ๋ฐ˜ํ™˜
            return old // ์›๋ž˜ colors๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜... ๋” ์ฑ„์šธ ํ•˜์–€์นธ์ด ์—†์œผ๋ฏ€๋กœ!
          }
          // ์ด๋ฏธ ๋ฐ”๋€ ์ƒ‰์€ old๋Œ€๋กœ ๋†”๋‘๊ณ 
          const copy = [...old]
          // ์ฐพ์•„๋‚ธ ์ฒซ ๋ฒˆ์งธ ์ธ๋ฑ์Šค์— hex๊ฐ’์„ ๋„ฃ๋Š”๋‹ค.
          copy[firstWhiteIndex] = hex
          return copy // newState !!!
        })
      }
      return (
        <div className="App">
          <article>
            <table className="selectedContainer flex flex-row">
              {/* map์„ ์จ์„œ ๋ฐฐ์—ด์˜ ๊ฐ’์„ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ค€๋‹ค. */}
              <tr>
                {colors.map((color) => (
                  //์Šคํƒ€์ผ์— map์œผ๋กœ ๋ฐ›์•„์˜จ color ๋ฌธ์ž์—ด ๊ฐ’์„ backgroundColor๋กœ ์ง€์ •ํ•ด์ค€๋‹ค.
                  <td
                    style={{
                      backgroundColor: color,
                    }}
                  >
                    {color}
                    {/* text node */}
                  </td>
                ))}
              </tr>
            </table>
            <div id="pallete" className="flex flex-row flex-wrap">
              {/* pallete๋„ ๊ฐ’์ด ์—ฌ๋Ÿฌ ๊ฐœ์ด๋ฏ€๋กœ map์„ ์‚ฌ์šฉํ•œ๋‹ค. */}
              {pallete.map((hex) => (
                <button
                  onClick={() => addSelected(hex)}
                  className="roundButton"
                  style={{ backgroundColor: hex }}
                >
                  {hex}
                </button>
              ))}
            </div>
          </article>
        </div>
      )
    }
    

    ๊ธฐ๋Šฅ 2. ์„ ํƒํ•œ ์นธ ์ƒ‰๊น” ์‚ญ์ œ(๋‹ค์‹œ ํ™”์ดํŠธ๋กœ) ๐Ÿฅš

    ์‹œ๋‚˜๋ฆฌ์˜ค

    Scenario: ๋ฐ•์Šค์˜ ์นธ์„ ํด๋ฆญํ•˜๋ฉด ์„ ํƒํ•œ ์นธ์ด ํฐ์ƒ‰์œผ๋กœ ๋ณ€ํ•œ๋‹ค
    Given ์„ ํƒ๋œ ์ƒ‰๊น”๋“ค๋กœ ๋ฌธ์ œ๋ฅผ ๋ Œ๋”ํ•˜๊ณ 
    When ๋ฐ•์Šค์˜ ์นธ์„ ํด๋ฆญํ•˜๋ฉด
    Then ์„ ํƒํ•œ ์นธ์ด ํฐ์ƒ‰์œผ๋กœ ๋ณ€ํ•œ๋‹ค
    

    ํŠน์ • ์นธ์„ ์„ ํƒํ•˜๋ฉด ๊ทธ ์นธ์˜ index๋ฅผ ๋ฐ›์•„์„œ ์„ ํƒ๋˜์ง€ ์•Š์€ ์นธ์€ ์ด์ „์˜ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋ฐ›์•„์˜ค๊ณ  ์„ ํƒ๋œ ์นธ์€ WHITE_HEX๋ฅผ ๋„ฃ์–ด์ค€๋‹ค. -> ๊ทธ๋Œ€๋กœ์ธ ๊ฐ’๊ณผ ์ƒˆ๋กœ์šด ๊ฐ’์„ ํ•ฉ์ณ ์ƒˆ state๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค!

    function deleteSelected(targetIndex: number) {
      setColors((old) => {
        const copy = [...old]
        // targetIndex์˜ ๊ฐ’์„ ํฐ์ƒ‰ ํ—ฅ์Šค๋ฅผ ๋„ฃ๋Š”๋‹ค.
        copy[targetIndex] = WHITE_HEX
        return copy // newState !!!
      })
    }
    

    ๊ธฐ๋Šฅ 3. ์„ ํƒํ•œ ์นธ ์ƒ‰๊น” ์žฌ์„ ํƒ - ๋นˆ ์นธ ์ˆœ์„œ๋Œ€๋กœ ๐Ÿฅš

    ์‹œ๋‚˜๋ฆฌ์˜ค

    Scenario: ์ฃผ๋ณด๊ฐ•์— ๊ฐ’์„ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค
    Given ๋น„์–ด์žˆ๋Š” ์นธ์„ ๋ Œ๋”ํ•˜๊ณ 
    When ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด
    Then ๊ฐ’์ด ์ž…๋ ฅ๋œ๋‹ค
    

    ์ฒ˜์Œ์— ์ž„์˜๋กœ ๋งŒ๋“  color๊ฐ’์€ ๊ฐ’์„ ๋งˆ์Œ๋Œ€๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, useState๋ฅผ ์‚ฌ์šฉํ•ด colors๋ฐฐ์—ด์„ ๋ฐ˜์‘ํ˜•(reactive)์ƒํƒœ๋กœ ๋งŒ๋“ ๋‹ค.

    const [colors, setColors] = React.useState(Array(BOX_COUNT).fill(WHITE_HEX)) // ๋ฌธ์ž์—ด
    

    ๊ทธ ๋‹ค์Œ ํ•˜์–€์ƒ‰์œผ๋กœ ์น ํ•ด์ง„ ๋นˆ ์นธ ์ค‘ ์ฒซ๋ฒˆ์งธ์นธ๋ถ€ํ„ฐ ์ˆœ์„œ๋Œ€๋กœ ๋‹ค์‹œ ์ƒ‰์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋„๋ก addSelected ํ•จ์ˆ˜์— ์ฒซ๋ฒˆ์งธ ํ•˜์–€์นธ index์— ์„ ํƒ๋œ hex๋ฅผ ๋„ฃ๋Š” ์‹์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

    function addSelected(hex: string) {
      setColors((old) => {
        // findIndex๋Š” ๋ฐฐ์—ด์˜ ๊ฐ’ ์ค‘์—์„œ ์กฐ๊ฑด์— ๋งž๋Š” ๊ฐ’์ด ์žˆ๋Š” ์ฒซ ๋ฒˆ์งธ index๋ฅผ ์ฐพ๋Š”๋‹ค!
        // hex ์ค‘์—์„œ hex์˜ ์ƒ‰์ด white์ธ ์ฒซ๋ฒˆ์งธ index๋ฅผ ์ฐพ์•„๋‚ธ๋‹ค.
        const firstWhiteIndex = old.findIndex((hex) => hex === WHITE_HEX)
        if (firstWhiteIndex === -1) {
          // ์ธ๋ฑ์Šค๋ฅผ ๋ชป ์ฐพ์œผ๋ฉด! -1์„ ๋ฐ˜ํ™˜
          return old // ์›๋ž˜ colors๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜... ๋” ์ฑ„์šธ ํ•˜์–€์นธ์ด ์—†์œผ๋ฏ€๋กœ!
        }
        // ์ด๋ฏธ ๋ฐ”๋€ ์ƒ‰์€ old๋Œ€๋กœ ๋†”๋‘๊ณ 
        const copy = [...old]
        // ์ฐพ์•„๋‚ธ ์ฒซ ๋ฒˆ์งธ ์ธ๋ฑ์Šค์— hex๊ฐ’์„ ๋„ฃ๋Š”๋‹ค.
        copy[firstWhiteIndex] = hex
        return copy // newState !!!
      })
    }