๐Ÿ˜‹ ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค 2020 Dev-Matching ์›น ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž(์ƒ๋ฐ˜๊ธฐ) ํ›„๊ธฐ

2020๋…„ 3์›” 14์ผ ํ† ์š”์ผ, ์˜คํ›„ 1์‹œ๋ถ€ํ„ฐ 5์‹œ๊นŒ์ง€ ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค์—์„œ ์ง„ํ–‰ํ–ˆ๋˜ ์›น ํ”„๋ก ํŠธ์—”๋“œ Dev-matching ์— ๋Œ€ํ•œ ํ›„๊ธฐ ๊ธ€์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ดํ›„ ๊ณผ์ œ๋ฅผ ๋‹ค์‹œ ํ’€์–ด๋ณด๋ฉฐ ๊ณผ์ •๊ณผ ์ƒ๊ฐ์„ ๊ธฐ๋กํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

devmatching

์‚ฌ๊ณผ emoji๋Š” ๊ณผ์ œํ…Œ์ŠคํŠธ์—์„œ ์š”๊ตฌํ•˜๋Š” ํ•„์ˆ˜ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.


1. ์‹ ์ฒญํ•˜๊ฒŒ ๋œ ๊ณ„๊ธฐ

์›น ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ผ๋Š” ๊ฟˆ์„ ํ–ฅํ•ด ํ•˜๋ฃจํ•˜๋ฃจ ์ฆ๊ฒ๊ฒŒ ๊ณต๋ถ€ํ•˜๊ณ ๋Š” ์žˆ์ง€๋งŒ ๋‚˜๋„ ์ด์ œ ๋ง‰ํ•™๊ธฐ์—ฌ์„œ ์ทจ์—…์— ๋Œ€ํ•œ ์ƒ๊ฐ๋„ ํ•˜์ง€ ์•Š์œผ๋ฉด ์•ˆ๋˜์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋˜ ๋„์ค‘ ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค๋ผ๋Š” ์‚ฌ์ดํŠธ์—์„œ ์›น ํ”„๋ก ํŠธ์—”๋“œ Dev-matching์ด๋ผ๋Š” ๊ฐœ๋ฐœ์ž ์ฑ„์šฉ ํ”„๋กœ๊ทธ๋žจ ์‹ ์ฒญ์„ ๋ฐ›๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค๋Š” ์ฝ”ํ…Œ ๋ฌธ์ œ๋ฅผ ํ’€๊ธฐ์œ„ํ•ด์„œ ์ž์ฃผ ์ด์šฉํ–ˆ์—ˆ๋Š”๋ฐ,

๋ฌธ์ œ์˜ ์ž…์ถœ๋ ฅ์ด ํ•จ์ˆ˜ํ˜•์œผ๋กœ ๋˜์–ด์žˆ์–ด์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ๋ฌธ์ œ๋ฅผ ํ‘ธ๋Š” ๋‚˜์—๊ฒŒ๋Š” ์ •๋ง ํŽธํ•˜๊ณ  ์ข‹์•˜๋‹ค.

์ด๋Ÿฌํ•œ ์ฑ„์šฉ ํ”„๋กœ๊ทธ๋žจ๋„ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์ฒ˜์Œ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ  ์ข‹์€ ๊ธฐํšŒ, ๊ฒฝํ—˜์ด ๋  ๊ฒƒ ๊ฐ™์•„ ๊ณ ๋ฏผ์—†์ด ์ง€์›ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

devmatching2

์ถœ์ฒ˜ : ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค

Dev-matching์€ ์ฝ”๋”ฉํ…Œ์ŠคํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ณผ์ œํ…Œ์ŠคํŠธ๋กœ ์ง„ํ–‰์ด ๋˜์—ˆ๊ณ  ๊ณผ์ œ๋Š” ์‹ค์ œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰๋œ๋‹ค๊ณ  ์ ํ˜€์ ธ์žˆ์—ˆ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ์—ญ๋Ÿ‰์„ ํŒ๋‹จํ•˜๊ธฐ์— ์ข‹์€ ํ…Œ์ŠคํŠธ๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

2. ์ค€๋น„

devmatching3

์ถœ์ฒ˜ : ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค

๊ณผ์ œํ…Œ์ŠคํŠธ์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์ •๋ณด๋„ ํ…Œ์ŠคํŠธ ์ด์ „์— ๊ณต๊ฐœ๋˜์—ˆ๋Š”๋ฐ, ์ด์ „์— ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๋กœ ๋ฐฐ๊ฒฝํ™”๋ฉด ๊ฒ€์ƒ‰ ํฌ๋กค๋ง ์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณธ ์ ์ด ์žˆ์–ด์„œ ๊ดœํžˆ ์ž์‹ ๊ฐ์ด ์ƒ๊ฒผ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๊ฐ€ ์–ด๋–ค ์‹์œผ๋กœ ๋‚˜์˜ฌ์ง€ ์ „ํ˜€ ๊ฐ์ด ์˜ค์ง€ ์•Š์•„์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฌธ์ž์—ด, ๋ฐฐ์—ด ๊ด€๋ จ ๋ฉ”์†Œ๋“œ๋“ค์ด๋‚˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ DOM์„ ์กฐ์ž‘ํ•˜๋Š” ํ•จ์ˆ˜๋“ค ์ •๋„๋งŒ ์ˆ™์ง€ํ•˜๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค.

3. ์ถœ์ œ๋ฌธ์ œ ๋‹ค์‹œ๋ณด๊ธฐ

๊ณผ์ œํ…Œ์ŠคํŠธ๋Š” ํ…Œ์ŠคํŠธ ์ด์ „์— ๊ณต๊ฐœ๋˜์—ˆ๋“ฏ์ด, ๊ณ ์–‘์ด ๊ฒ€์ƒ‰ ์›น์‚ฌ์ดํŠธ์˜ ๊ธฐ๋ณธ ํ‹€์ด ์ฃผ์–ด์กŒ๋‹ค.

devmatching4

๋‹ค์‹œ๋งŒ๋“ค์–ด๋ณธ ๊ณ ์–‘์ด ๊ฒ€์ƒ‰ ์›น์‚ฌ์ดํŠธ (๊ณผ์ œ)

3-1. ์ฝ”๋“œ ๊ตฌ์กฐ

์ฒ˜์Œ์—๋Š” ์ฃผ์–ด์ง„ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๋Š”๋ฐ๋งŒ ์—„์ฒญ ์˜ค๋ž˜๊ฑธ๋ ธ๋‹ค.

๋จผ์ € index.html์˜ bodyํƒœ๊ทธ ์•ˆ์—๋Š” ์ „์ฒด ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋‹ด๋‹นํ•  <div> ํ•˜๋‚˜๋งŒ ์กด์žฌํ•˜์˜€๊ณ , ๋ชจ๋“  ํ”„๋กœ๊ทธ๋žจ์€ <script>๋กœ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์™€ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์‹์ด์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  ES6 class ๋ฌธ๋ฒ•์ด ์ฃผ๋œ ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์„ฑ ๋ฐฉ์‹์ด์—ˆ์œผ๋ฉฐ, ๊ฐ๊ฐ์˜ class ๋‚ด๋ถ€์—๋Š” setState ๋ฉ”์†Œ๋“œ์™€ render ๋ฉ”์†Œ๋“œ๊ฐ€ ์ •์˜๋˜์–ด์žˆ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  constructor ๋‚ด๋ถ€์— render() ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์žˆ์–ด์„œ constructor ์‹œํ–‰ ํ›„, render() ๋ฅผ ์‹œํ–‰ํ•˜๋ฉฐ DOM์„ ๊ตฌ์„ฑํ•˜๋Š” ์‹์ด์—ˆ๋‹ค.

๋ฌธ์ œ๋Š” ๋ชจ๋‘ javascript๋กœ ๋˜์–ด์žˆ์—ˆ์ง€๋งŒ, setState ๋ฆฌ๋ Œ๋”๋ง์ด๋‚˜ render() ์•ˆ์˜ innerHTML์„ ํ†ตํ•ด ์ž์†DOM์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ, class ๋‚ด๋ถ€ state๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๋“ฑ์˜ ์ฝ”๋“œ๊ตฌ์กฐ๊ฐ€ ๋ฆฌ์•กํŠธ์™€ ๋ฌด์ฒ™ ๋น„์Šทํ•˜๋‹ค๋Š” ๋Š๋‚Œ์„ ๋ฐ›์•˜๋‹ค.

// ์ด๋ฏธ์ง€์ƒ์„ธ๋ณด๊ธฐ์ฐฝ js
class ImageInfo {
  $imageInfo = null
  data = null

  constructor({ $target, data }) {
    const $imageInfo = document.createElement('div')
    $imageInfo.className = 'ImageInfo'
    this.$imageInfo = $imageInfo
    $target.appendChild($imageInfo)

    this.data = data

    this.render()
  }

  setState(nextData) {
    this.data = nextData
    this.render()
  }

  render() {
    if (this.data.visible) {
      const { name, url, temperament, origin } = this.data.image

      this.$imageInfo.innerHTML = `
        <div class="content-wrapper">
          <div class="title">
            <span>${name}</span>
            <div class="close">x</div>
          </div>
          <img src="${url}" alt="${name}"/>        
          <div class="description">
            <div>์„ฑ๊ฒฉ: ${temperament}</div>
            <div>ํƒœ์ƒ: ${origin}</div>
          </div>
        </div>`
      this.$imageInfo.style.display = 'block'
    } else {
      this.$imageInfo.style.display = 'none'
    }
  }
}

์ด์ฒ˜๋Ÿผ ์ฝ”๋“œ๋Š” class์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ฌ์œผ๋กœ์จ, DOM์„ ๋ Œ๋”๋งํ•˜์˜€๊ณ , ํ•ด๋‹น ์ฝ”๋“œ์—๋Š” ์—†์ง€๋งŒ setState() ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฆฌ๋ Œ๋”๋งํ•˜๋Š” ๋“ฑ classํ˜•์‹์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์€ ๋ฆฌ์•กํŠธ์˜ ์ปดํฌ๋„ŒํŠธ์™€ ๋งŽ์ด ๋‹ฎ์•„์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

3-2. HTML, CSS ๊ด€๋ จ

์‹œ๋งจํ‹ฑ ํƒœ๊ทธ ์ž‘์„ฑ

์ฒซ ๋ฌธ์ œ๊ฐ€ ์งœ์—ฌ์ง„ ์ฝ”๋“œ๋ฅผ ์‹œ๋งจํ‹ฑํ•˜๊ฒŒ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด์—ˆ๋Š”๋ฐ ํ‰์†Œ์— div ์™ธ์˜ ๋‹ค๋ฅธ ํƒœ๊ทธ์˜ ์กด์žฌ๋งŒ ์•Œ๊ณ ์žˆ์—ˆ๋˜ ๋‚˜๋Š” ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋‹ค์Œ ๋ฌธ์ œ๋กœ ๋„˜์–ด๊ฐ”์ง€๋งŒ, ์ฝ”๋“œ ์—ญ์‹œ ํ•˜๋‚˜์˜ ์–ธ์–ด์ด๊ณ  ๊ฐ๊ฐ ์š”์†Œ๋“ค์„ ๋ช…์‹œํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๊ฒ ๊ตฌ๋‚˜ ๋Š๋ผ๊ฒŒ ๋˜์—ˆ๋‹ค.


๋ฐ˜์‘ํ˜• ์ฒ˜๋ฆฌ

ํฌ๋กค๋งํ•œ ๊ณ ์–‘์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋””๋ฐ”์ด์Šค๋ณ„๋กœ ๋ ˆ์ด์•„์›ƒ์„ ๋‹ฌ๋ฆฌํ•ด์ฃผ๋Š” ๋ฌธ์ œ๋„ ์žˆ์—ˆ๋‹ค. ์šฐ์„  ์ด๋ฏธ์ง€ ๋ ˆ์ด์•„์›ƒ ๋ฐฉ์‹์€ grid ์˜€๋Š”๋ฐ, ํ‰์†Œ ๋ ˆ์ด์•„์›ƒ์žก์„ ๋•Œ, flex๋ฐ–์— ์•ˆํ•ด์„œ ๋‹นํ™ฉํ•˜๊ธด ํ–ˆ์ง€๋งŒ @media ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ๋””๋ฐ”์ด์Šค width ์— ๋”ฐ๋ผ grid-template-columns ์†์„ฑ์„ ๋‹ค๋ฅด๊ฒŒ ํ•ด์ฃผ์—ˆ๋‹ค.


๋‹คํฌ๋ชจ๋“œ

OS ๋‹คํฌ๋ชจ๋“œ ์„ค์ •์— ๋”ฐ๋ฅธ CSS ๋ณ€๊ฒฝ๊ณผ ์ง์ ‘ ํ…Œ๋งˆ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋Š” ํ† ๊ธ€ ๋ฐ•์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๋ฌธ์ œ๋„ ์žˆ์—ˆ๋‹ค. @media ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•ด OS ๋‹คํฌ๋ชจ๋“œ ์„ค์ •์„ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๊ตฌ๋‚˜(@media (prefers-color-scheme: dark))์— ๋Œ€ํ•ด์„œ ์•Œ ์ˆ˜ ์žˆ์—ˆ๊ณ  ํ† ๊ธ€ ๋ฐ•์Šค๋Š” ๋งŒ๋“œ๋Š”๋ฐ๋Š” ์–ด๋ ต์ง€ ์•Š์•˜์ง€๋งŒ CSS ์†์„ฑ ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ ํ…Œ๋งˆ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ๋‹คํฌ๋ชจ๋“œ์˜ CSS ์†์„ฑ์„ ๋”ฐ๋ผ๊ฐ€๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.ใ… ใ…  ๋‚ด ๋ธ”๋กœ๊ทธ์—๋„ ํ…Œ๋งˆ ํ† ๊ธ€ ๋ฐ•์Šค๊ฐ€ ์žˆ๋Š”๋ฐ ์ฐธ๊ณ ํ•˜๋ฉด์„œ ๊ณต๋ถ€ํ•ด๋ด์•ผ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

3-3. ์ด๋ฏธ์ง€ ์ƒ์„ธ๋ณด๊ธฐ ๋ชจ๋‹ฌ

๋ฐ˜์‘ํ˜• ์ฒ˜๋ฆฌ

์ด๋ฏธ์ง€๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ, ์ƒ์„ธ๋ณด๊ธฐ ๋ชจ๋‹ฌ์ด ๋œจ๋Š”๋ฐ, ์ด ๋˜ํ•œ ๋””๋ฐ”์ด์Šค์— ๋”ฐ๋ฅธ ํฌ๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์–ด์•ผ ํ–ˆ๋‹ค.


๋ชจ๋‹ฌ ๋‹ซ๊ธฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๐ŸŽ

๋’ค์— ๋ถ™์€ ์‚ฌ๊ณผ emoji๋Š” ํ…Œ์ŠคํŠธ์—์„œ ์š”๊ตฌํ•˜๋Š” ํ•„์ˆ˜๊ธฐ๋Šฅ์ด๋‹ค. ์ด๋Ÿฌํ•œ ํ•„์ˆ˜ ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ›„๊ธฐ๊ธ€์„ ํฌ์ŠคํŒ…ํ•˜๋Š” ์ง€๊ธˆ ์‹œ์ ์— ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.. ใ„ดใ…‡ใ„ฑ!! ๊ณผ์ œ ๋ฉ”๋‰ด์–ผ์„ ๋ณต์‚ฌํ•ด์„œ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์˜ฎ๊ธด ํ›„ ๊ณผ์ œ๋ฅผ ์ง„ํ–‰ํ–ˆ๋Š”๋ฐ ๋ณต์‚ฌํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ˆ„๋ฝ๋˜์„œ ๋ชป๋ดค๋‚˜๋ณด๋‹คโ€ฆใ… ใ… 

์ฃผ์–ด์ง„ ์ฝ”๋“œ์—๋Š” ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ ๊ธฐ๋Šฅ์ด ์•„์˜ˆ ์—†์—ˆ๊ณ , ๋ชจ๋‹ฌ ์˜์—ญ ๋ฐ–์„ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ / ํ‚ค๋ณด๋“œ์˜ ESC ํ‚ค๋ฅผ ๋ˆ„๋ฅด๊ฑฐ๋‚˜ / ๋ชจ๋‹ฌ ์šฐ์ธก์˜ ๋‹ซ๊ธฐ(x) ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋‹ซํžˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ์งœ๋Š” ๋ฌธ์ œ์˜€๋‹ค.

๋ชจ๋‹ฌ x ๋ฒ„ํŠผ์— onclick ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ์™€, window ๊ฐ์ฒด์— onkeydown, onclick์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•ด์ฃผ๋ฉด ๋˜์—ˆ๋‹ค.


๊ณ ์–‘์ด ์ •๋ณด ๋ถˆ๋Ÿฌ์˜ค๊ธฐ (AJAX)

ํฌ๋กค๋งํ•œ ๋ฐ์ดํ„ฐ๋Š” api ํ˜•ํƒœ๋กœ ์ฃผ์–ด์กŒ์œผ๋ฉฐ /cats/:id ์— GET ์š”์ฒญ์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜ค๋ฉด ๋˜์—ˆ๋‹ค.

AJAX ๋น„๋™๊ธฐ์š”์ฒญ์ด ์™„๋ฃŒ๋œ ํ›„์— ํ•ด๋‹น ๋ชจ๋‹ฌ์ฐฝ์— ๋ฐ›์€ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋ Œ๋”๋ง ๋˜๊ฒŒ๋” ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ Promise then() ๋ฉ”์†Œ๋“œ์ฒด์ด๋‹์„ ํ†ตํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

3-4. ๊ฒ€์ƒ‰ ํŽ˜์ด์ง€

autofocus , ๊ฒ€์ƒ‰์ฐฝ ๋น„์šฐ๊ธฐ

ํŽ˜์ด์ง€ ์ง„์ž…์‹œ ๊ฒ€์ƒ‰์ฐฝ์œผ๋กœ focus๋ฅผ ์ด๋™์‹œํ‚ค๊ณ  ๊ฒ€์ƒ‰์ฐฝ์— ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•œ ์ƒํƒœ์—์„œ ๊ฒ€์ƒ‰์ฐฝ ์žฌํด๋ฆญ์‹œ ๊ธฐ์กด์˜ ํ‚ค์›Œ๋“œ๋ฅผ ์‚ญ์ œํ•˜๋ผ๋Š” ๋ฌธ์ œ์˜€๋‹ค. ๋น„๊ต์  ์‰ฝ๊ฒŒ input์˜ autofocus ์†์„ฑ์„ true๋กœ ํ•˜๊ณ , click ์ด๋ฒคํŠธ ์‹œ, input value๋ฅผ ๋นˆ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พธ๊ฒŒ ํ•˜์—ฌ์„œ ํ•ด๊ฒฐํ•˜์˜€๋‹ค.


๋ฐ์ดํ„ฐ ๋กœ๋”ฉ UI ๊ตฌํ˜„ ๐ŸŽ

๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž„์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆฌ๋Š” UI๋ฅผ ๋งŒ๋“œ๋Š” ๋ฌธ์ œ์˜€๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๋ฉด div.SearchResult ์„ ํƒ์ž ํƒœ๊ทธ ์•ˆ์˜ innerHTML ์†์„ฑ๊ฐ’์œผ๋กœ ์ •๋ณด๋ฅผ ๋„ฃ๋Š” ๊ตฌ์กฐ์˜€๋Š”๋ฐ, ๋ฐ์ดํ„ฐ ๋น„๋™๊ธฐ ์š”์ฒญ ์ „์— innerHTML๋กœ ๋กœ๋”ฉ์ค‘์ด๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ ๋„ฃ์–ด์ฃผ์–ด์„œ ์–ด์ฐจํ”ผ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๊ฐ€ ๋˜๋ฉด innerHTML๋กœ ์ •๋ณด๊ฐ€ ๋ฎ์–ด์จ์ง€๋Š” ๊ฒƒ์„ ์ด์šฉํ•ด์„œ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

devmatching5


๋ฐ์ดํ„ฐ ์—†์Œ UI ๊ตฌํ˜„ ๐ŸŽ

๋ Œ๋” ํ•จ์ˆ˜ ๋‚ด๋ถ€ ์กฐ๊ฑด๋ฌธ ์ฒ˜๋ฆฌ๋กœ ์‘๋‹ต๋ฐ›๋Š” ๋ฐ์ดํ„ฐ๋ฐฐ์—ด ๊ธธ์ด๊ฐ€ 0์ผ๊ฒฝ์šฐ, โ€˜๋ฐ์ดํ„ฐ ์—†์Œโ€™ ์ด๋ผ๋Š” ํ…์ŠคํŠธ๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.


์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด ๊ธฐ๋Šฅ

ํ‚ค์›Œ๋“œ๋ฅผ ๊ฒ€์ƒ‰ํ–ˆ์„ ๋•Œ, ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด 5๊ฐœ๋ฅผ ๋‚จ๊ธฐ๊ณ  ํ•ด๋‹น ๊ฒ€์ƒ‰์–ด๋ฅผ ๋ˆ„๋ฅด๋ฉด ๊ฒ€์ƒ‰๋„ ๊ฐ€๋Šฅํ•˜๊ฒŒ๋” ํ•˜๋Š” ๋ฌธ์ œ์˜€๋‹ค. ํ…Œ์ŠคํŠธ ์ค‘์—๋Š” ๊ฒ€์ƒ‰์–ด 5๊ฐœ ๋ณด์—ฌ์ง€๋Š” ๊ฑฐ๋งŒ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ํ…Œ์ŠคํŠธ ๋๋‚˜๊ณ  ๊ฒ€์ƒ‰์–ด ํด๋ฆญ ์‹œ, ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅ๋˜๊ฒŒ๋” ๋‹ค์‹œ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

const TEMPLATE = '<input type="text">'

class SearchInput {
  $latestDOM = null
  latest_arr = []
  onSearch = null

  constructor({ $target, onSearch }) {
    const $searchInput = document.createElement('input')
    const $latestDOM = document.createElement('div')
    this.$latestDOM = $latestDOM
    this.$searchInput = $searchInput
    $searchInput.autofocus = true
    this.$searchInput.placeholder = '๊ณ ์–‘์ด๋ฅผ ๊ฒ€์ƒ‰ํ•ด๋ณด์„ธ์š”.|'
    this.onSearch = onSearch

    $searchInput.className = 'SearchInput'
    $target.appendChild($searchInput)
    $target.appendChild($latestDOM)

    $searchInput.addEventListener('keypress', e => {
      if (e.keyCode === 13) {
        this.latest_arr.unshift(e.target.value)
        if (this.latest_arr.length > 5) {
          this.latest_arr.pop()
        } else {
        }
        console.log(this.latest_arr, e.target.value)
        this.render()
        onSearch(e.target.value)
      }
    })

    $searchInput.addEventListener('click', () => {
      $searchInput.value = ''
    })

    console.log('SearchInput created.', this)
  }

  render() {
    this.$latestDOM.innerHTML = this.latest_arr
      .map(item => {
        return `<span class="latest" style="cursor:pointer; border:1px solid black; padding: 3px; margin:3px">${item}</span>`
      })
      .join('')

    this.$latestDOM.querySelectorAll('.latest').forEach(($item, index) => {
      $item.addEventListener('click', e => {
        this.onSearch(this.latest_arr[index])
      })
    })
  }
}

๋จผ์ € ๊ฒ€์ƒ‰์–ด๊ฐ€ ์ €์žฅ๋  DOM ์„ ์ƒ์„ฑํ•˜๊ณ , ๋ถ€๋ชจ ๋…ธ๋“œ์— DOM์„ ์ถ”๊ฐ€ํ•ด์ค€ ๋’ค, ๊ฒ€์ƒ‰(keypress) ์‹œ, innerHTML ์„ ํ†ตํ•ด, ๋‚ด๋ถ€ ์š”์†Œ๋“ค๋„ ๋งŒ๋“ค๊ณ , click ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋„ ๊ฐ™์ด ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ดˆ๊ธฐ ์ฝ”๋“œ์—์„œ๋Š” ๊ฒ€์ƒ‰ ์ด๋ฒคํŠธ๊ฐ€ keypress ๊ฐ€ ์•„๋‹ˆ๋ผ keyup ์ด์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ keyup ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œ, ํ‚ค์›Œ๋“œ๊ฐ€ ์˜์–ด์ผ ์‹œ๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ ํ•œ๊ธ€ ํ‚ค์›Œ๋“œ์ผ ๊ฒฝ์šฐ, ๋‘๋ฒˆ ์ณ์ง€๋Š” ํ˜„์ƒ์ด ์ผ์–ด๋‚˜์„œ keypress ๋กœ ๋ฐ”๊พธ์–ด์ฃผ์—ˆ๋‹ค.


ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ, ๋งˆ์ง€๋ง‰ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ ์œ ์ง€

์ƒˆ๋กœ๊ณ ์นจ์‹œ, ๋งˆ์ง€๋ง‰ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ๋ฅผ ์œ ์ง€ํ•˜๋ผ๋Š” ๋ฌธ์  ๋ฐ, ํ…Œ์ŠคํŠธ ์ค‘์—๋Š” ์ฟ ํ‚ค๋ฅผ ์ด์šฉํ•ด์•ผ๋ ์ง€ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ๋ ์ง€ ๊ฐ์ด ์•ˆ์™€์„œ ํ…Œ์ŠคํŠธ ์ค‘์—๋Š” ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๊ณ  ํ…Œ์ŠคํŠธ ๋๋‚œ ํ›„์— ๋‹ค์‹œ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

window ๊ฐ์ฒด ๋‚ด๋ถ€์— localStorage ์†์„ฑ์„ ์ด์šฉํ•˜๋ฉด ๋งˆ์ง€๋ง‰ ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ๋ฅผ ๋กœ์ปฌ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

myStorage = window.localStorage

localStorage ์†์„ฑ์˜ setItem() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ๋งˆ์ง€๋ง‰ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ๋ฅผ ๋กœ์ปฌ์— ์ €์žฅํ•ด๋’€๋‹ค๊ฐ€ ๋‚˜์ค‘์— ์ƒˆ๋กœ๊ณ ์นจํ•˜์—ฌ๋„ DOM์ด ๋กœ๋“œ๋œ ํ›„, getItem() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด ์ €์žฅํ–ˆ๋˜ ๊ฒฐ๊ณผ๋ฅผ ์ด์šฉํ•ด ํฌ๋กค๋ง๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋งˆ์ง€๋ง‰ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.


๋žœ๋ค ๊ณ ์–‘์ด ๊ฒ€์ƒ‰ ๋ฒ„ํŠผ ๊ตฌํ˜„ ๐ŸŽ

์ƒˆ๋กœ์šด api ์ฃผ์†Œ(GET /api/cats/random50)๋ฅผ ์ด์šฉํ•ด ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ๋žœ๋ค ๊ณ ์–‘์ด๋“ค์ด ํ™”๋ฉด์— ๊ทธ๋ ค์ง€๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฌธ์ œ์ธ๋ฐ, ํ•„์ˆ˜๋ฌธ์ œ์ธ ๊ฒƒ๋„ ๋ชจ๋ฅด๊ณ  ์•ž์—์„œ ์‹œ๊ฐ„์„ ๋‹ค์จ๋ฒ„๋ ค์„œ ๋ชปํ’€์—ˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ ๋๋‚˜๊ณ  ํ’€์–ด๋ณด๋‹ˆ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์—ˆ๋‹ค.

// App.js

this.searchInput = new SearchInput({
  $target,
  onSearch: keyword => {
    api.fetchCats(keyword).then(({ data }) => this.setState(data))
  },
  onRandomClick: () => {
    api3.fetchCats().then(({ data }) => this.setState(data))
  },
})

App.js ์˜ SearchInput ๊ฐ์ฒด์— onRandomClick ๋ฉ”์†Œ๋“œ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. ๊ฒ€์ƒ‰์‹œ ์‘๋‹ต๋ฐ›๋Š” ๋ฐ์ดํ„ฐ ํ˜•์‹์ด ๊ฐ™์•„์„œ api๋ช…๋งŒ ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  SearchInput.js ์—์„œ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค๊ณ  ๊ทธ ๋ฒ„ํŠผ click ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ์— ์ „๋‹ฌ๋ฐ›์€ onRandomClick() ๋งŒ ๋„ฃ์–ด์ฃผ๋ฉด ๋˜๋Š” ๋ฌธ์ œ์˜€๋‹ค.


์ด๋ฏธ์ง€ lazy load ๊ตฌํ˜„

lazy load ๊ฐœ๋…์ด ์ƒ์†Œํ–ˆ๋Š”๋ฐ ๋‚˜์ค‘์— ๋‚ ์žก๊ณ  ํ•™์Šต์„ ํ•ด์•ผ๊ฒ ๋‹ค.

3-5. ์Šคํฌ๋กค ํŽ˜์ด์ง•

์Šคํฌ๋กค์ด ๋ฐ”๋‹ฅ์— ๋‹ฟ์•˜์„ ์‹œ, ๋‹ค์ŒํŽ˜์ด์ง€๋ฅผ ๋กœ๋”ฉํ•˜๋ผ๋Š” ๋ฌธ์ œ์ด๋‹ค.

window ๊ฐ์ฒด์— scroll ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  scrollHeight์™€ scrollY,clientHeight๋ฅผ ์ด์šฉํ•ด์„œ ์Šคํฌ๋กค์ด ๋ฐ”๋‹ฅ์— ๋‹ฟ์„ ๋•Œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ๊นŒ์ง€๋Š” ํ–ˆ๋Š”๋ฐ ๋‹ค์Œ ํŽ˜์ด์ง€๋กœ ๊ฐ€๋Š” ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ๋‹ค. api ์š”์ฒญ๋ถ€๋ถ„์—๋„ ํŽ˜์ด์ง€๋ถ€๋ถ„์ด ์—†์–ด์„œ ์šฐ์„ ์€ console์ฐฝ์—๋งŒ ๋œจ๊ฒŒ๋”ํ•˜์˜€๋‹ค.

3-6. ์ฝ”๋“œ ๊ตฌ์กฐ ๋ณ€๊ฒฝ

ES6 ๋ชจ๋“ˆ ํ˜•ํƒœ๋กœ ์ฝ”๋“œ๋ณ€๊ฒฝ

์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. ใ…œ


fetch -> async await ๋ณ€๊ฒฝ

ES7 ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฌธ๋ฒ•์ธ async await ์„ ์‚ฌ์šฉํ•ด์„œ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ผ๋Š” ๋ฌธ์ œ์˜€๋‹ค.

๋ณ€๊ฒฝ์ „.

const api3 = {
  fetchCats: () => {
    return fetch(`${API_ENDPOINT}/api/cats/random50`).then(res => res.json())
  },
}

๋ณ€๊ฒฝํ›„.

const api3 = {
  fetchCats: async () => {
    const response = await fetch(`${API_ENDPOINT}/api/cats/random50`)
    return response.json()
  },
}

API ์˜ status code ์— ๋”ฐ๋ผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์ž‘์„ฑ ๐ŸŽ

๋ณ€๊ฒฝํ›„.

const api3 = {
  fetchCats: async () => {
    const response = await fetch(`${API_ENDPOINT}/api/cats/random50`)
    if (response.status === 200) {
      return response.json()
    } else {
      console.error(`${response}์—๋Ÿฌ : ${response.statusText}`)
    }
  },
}

๊ทผ๋ฐ ์ด๊ฒŒ ๋งž๋Š” ๋‹ต์ธ์ง€๋Š” ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค. ๐Ÿ˜…


์ด๋ฒคํŠธ ์œ„์ž„ (Event Delegation)

SearchResult ์— ๊ฐ ์•„์ดํ…œ์„ ํด๋ฆญํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ Event Delegation ๊ธฐ๋ฒ•์„ ์ด์šฉํ•ด ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”. ๋ผ๋Š” ๋ฌธ์ œ์˜€๋‹ค.

์ฆ‰, ๊ฒ€์ƒ‰ํ•ด์„œ ๋‚˜์˜จ ๊ณ ์–‘์ด๋ฅผ ํด๋ฆญํ•  ๋•Œ, ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ณ ์–‘์ด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ๊ณ ์–‘์ด ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ์— ๋“ฑ๋กํ•˜๋Š” ๋ฌธ์ œ์˜€๋‹ค.

๋จผ์ € ๊ธฐ์กด์˜ ์ฝ”๋“œ.

this.$searchResult.querySelectorAll('.item').forEach(($item, index) => {
  $item.addEventListener('click', () => {
    this.onClick(this.data[index])
  })
})

์ˆ˜์ •ํ•œ ๋ถ€๋ถ„.

this.$searchResult.innerHTML = this.data
  .map(
    (cat, i) => `
            <div class="item">
              <img src=${cat.url} alt=${cat.name} name=cat${i}/>
            </div>
          `
  )
  .join('')

๊ณ ์–‘์ด ์‚ฌ์ง„๋“ค์— name์†์„ฑ์— cat + index๋ฅผ ๋ถ™์—ฌ์ฃผ์—ˆ๋‹ค.

this.$searchResult.addEventListener('click', e => {
  if (e.target.name) {
    this.onClick(this.data[e.target.name.substr(3, e.target.name.length - 4)])
  }
})

๊ทธ๋ฆฌ๊ณ  ๊ณ ์–‘์ด ์‚ฌ์ง„ ์ปจํ…Œ์ด๋„ˆ์— click ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ํด๋ฆญํ•œ ๋ถ€๋ถ„์ด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ€๋„๋ก ์กฐ๊ฑด๋ฌธ ์ฒ˜๋ฆฌ๋„ ํ•ด์ฃผ์—ˆ๋‹ค. ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” index ๋ถ€๋ถ„์ด๋ฏ€๋กœ ์•ž์˜ cat๋ถ€๋ถ„์„ substr()๋กœ ์ œ๊ฑฐํ•ด์ฃผ์—ˆ๋‹ค.

3-6. ํ…Œ์ŠคํŠธ ๊ด€๋ จ(๊ฐ€์‚ฐ์ )

๋ฌธ์ œ

* Test suite์™€ ๊ฐ test ์˜ ๋ชฉ์ ์„ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๊ธฐ์ˆ ํ•ด์ฃผ์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด,


isNumber test (x)
isNumber ํ•จ์ˆ˜๋Š” number type ์˜ argument ๋ฅผ ๋ฐ›์œผ๋ฉด True ๋ฅผ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. (o)


* ๊ฐ ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์— ์žˆ๋Š” ํ•จ์ˆ˜๋“ค์ด๋‚˜, Util ํ•จ์ˆ˜๋“ค์„ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
* ์กฐ๊ฑด๋ฌธ์ด ์žˆ๋Š” ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ, edge case์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
* ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋‚ด์—์„œ ๊ฐ ํ…Œ์ŠคํŠธ๋งˆ๋‹ค ๋ฐ˜๋ณต์ ์œผ๋กœ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ life cycle ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ๊ด€๋ฆฌํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ „ํ˜€ ๋ฌด์Šจ๋ง์ธ์ง€ ๋ชจ๋ฅด๊ฒ ์–ด์„œ PASS..ํ•˜์˜€๋‹ค

4. ๋งˆ์น˜๋ฉฐ

์ด๋ฒˆ ํ…Œ์ŠคํŠธ๋Š” ์‹ค๋ ฅ์žˆ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋ฅผ ์ฑ„์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด์ง€๋งŒ, ํ”„๋ก ํŠธ์—”๋“œ ๋ถ„์•ผ๋ฅผ ๊ณต๋ถ€ํ•˜๋Š” ์ž…์žฅ์—์„œ๋„ ์ •๋ง ์ข‹์€ ๊ฒฝํ—˜์ด ๋˜์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ๊ฐœ์ธํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ์ ‘ํ•ด๋ณด์ง€ ๋ชปํ–ˆ๋˜ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์‚ฌ์šฉ์ž์ธก๋ฉด ํ˜น์€ ๊ฐœ๋ฐœ์ž์ธก๋ฉด์˜ ์ด์Šˆ๋“ค์ด๋‚˜ lazy load๋‚˜ ์ด๋ฒคํŠธ์œ„์ž„๊ฐ™์€ ์ƒ์†Œํ–ˆ๋˜ ๊ฐœ๋…๋“ค๋„ ์•Œ๊ฒŒ ๋˜์–ด์„œ ๋„ˆ๋ฌด ์ข‹์•˜๋‹ค.

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

ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•œ ๋ฌธ์ œ๋‚˜ ๋ถ€์กฑํ•˜๋‹ค๊ณ  ์ƒ๊ฐ๋˜๋Š” ๋ถ€๋ถ„์€ ๋”ฐ๋กœ ์ •๋ฆฌํ•ด๋’€๋‹ค๊ฐ€ ๋‹ค์‹œ ๊ณต๋ถ€ํ•ด์•ผ๊ฒ ๋‹ค.

๐ŸŽ‰ 2020-03-19


Written by@taenyKim
๋ฐฐ์šฐ๋ฉฐ ์„ฑ์žฅํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ #FE #UI #๊ฐœ๋ฐœ #life

GitHubFacebook