๐Ÿ—“ JS๋กœ ๋‹ฌ๋ ฅ ๋งŒ๋“ค๊ธฐ

๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—†์ด Vanilla JS๋งŒ์œผ๋กœ ๋‹ฌ๋ ฅ์„ ์ž‘์„ฑํ•ด๋ณด์ž. ์šฐ์•„ํ•œํ…Œํฌ์บ ํ”„ ๋‹น์‹œ 3๋ฒˆ์งธ ํ”„๋กœ์ ํŠธ "๋ฑ…ํฌ์ƒ๋Ÿฌ๋“œ ํด๋ก  ๋งŒ๋“ค๊ธฐ"์—์„œ ๋‚ด๊ฐ€ ๋‹ด๋‹นํ–ˆ๋˜ ํŒŒํŠธ์ด๋‹ค. ๋ณต์Šตํ•˜๊ณ  ์ •๋ฆฌํ•˜๋Š” ์ฐจ์›์—์„œ ๋‹ค์‹œ ์ž‘์„ฑํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

์™„์„ฑ๋ณธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ

๋ชฉํ‘œ

  • DOM ์ƒ์„ฑ ์ดํ›„ DOM ์กฐ์ž‘ํ•˜์ง€ ์•Š๊ธฐ. ์ฆ‰ ๋ชจ๋“  ์ž‘์—…์€ ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์‹œ์ ์—์„œ ๋๋‚ด๊ธฐ. document.querySelector์™€ ๊ฐ™์€ DOM API๋ฅผ ์‚ฌ์šฉํ•˜๊ณ ์ž ์•Š๊ณ ์ž ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋Š” "์ด์ชฝ์—์„œ A๋กœ ์กฐ์ž‘ํ•˜๊ณ  ์ €์ชฝ์—์„œ B๋กœ ์กฐ์ž‘ํ•˜๊ณ  ๋˜ ๋ฐ˜๋Œ€ํŽธ์—์„œ C๋กœ ์กฐ์ž‘ํ•˜๊ณ  ์ˆœ์„œ ๊ผฌ์ด๊ณ  ์ฝ”๋“œ ์—‰ํ‚ค๊ณ " ๊ฐ™์€ ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค. ๋‹จ, ์ฒ˜์Œ ์•ฑ์„ ์„ ํƒํ•˜์—ฌ initializeํ•  ๋•Œ๋งŒ document.querySelector('#App')์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • OOP ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋Œ€์‹  ์ž‘์€ ํ•จ์ˆ˜๋“ค๋กœ ์ž‘์„ฑํ•˜๊ธฐ

์‚ฌ์šฉํ•  ์Šคํƒ

  • Date Object (Vanilla JS)
  • Display: Grid (CSS)

1. index.html ์ž‘์„ฑํ•˜๊ธฐ

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Calendar + Grid</title>
  </head>

  <body>
    <div id="App"></div>
  </body>
</html>

VS Code์˜ ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

2. calendar.js ์ž‘์„ฑํ•˜๊ธฐ

์šฐ์„  index.html์˜ head ํƒœ๊ทธ ์•ˆ์— ์ฝ”๋“œ๋ฅผ ์—ฐ๊ฒฐํ•˜์ž.

<script src="calendar.js"></script>

2-1. calendar.js์— ์‚ฌ์šฉํ•  util ํ•จ์ˆ˜ ์ž‘์„ฑํ•˜๊ธฐ

html string highlighting์„ ์œ„ํ•œ html ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ด ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  backtick์œผ๋กœ ๊ฐ์‹ธ์ง„ JS String ์•ž์— html ๊ธ€์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด JS String์„ html์ฒ˜๋Ÿผ ํ•˜์ด๋ผ์ดํŒ…ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

const html = (s, ...args) => s.map((ss, i) => `${ss}${args[i] || ''}`).join('')

๋งค์ง๋„˜๋ฒ„๋ฅผ ์—†์• ๊ธฐ ์œ„ํ•ด const๋“ค์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค. ์ฝ”๋“œ ์ค‘ ๊ฐ‘์ž๊ธฐ 7์ด ํŠ€์–ด๋‚˜์˜ค๋ฉด ์–ด๋А ๋งฅ๋ฝ์˜ 7์ธ์ง€ ์•Œ๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

const NUMBER_OF_DAYS_IN_WEEK = 7
const NAME_OF_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']

๊ฐ€์žฅ ๊ธฐ์ดˆ๊ฐ€ ๋  renderCalendar๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์—ˆ๋‹ค. ๋˜ํ•œ renderCalendar๋ฅผ ๊ธฐ์กด index.html ์ตœํ•˜๋‹จ์— ์—ฐ๊ฒฐํ•ด์ฃผ์—ˆ๋‹ค.

const renderCalendar = ($target) => {
  $target.innerHTML = getCalendarHTML()
}
<script>
  renderCalendar(document.querySelector('#App'))
</script>

๋‹ฌ๋ ฅ์„ ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•ด์„œ ์ด 4๊ฐœ์˜ Date ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค. ๋ฌผ๋ก  ๋” ์ ์€ Date ๊ฐ์ฒด๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๋‹ฌ๋ ฅ์— ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ๋“ค์— ๋”ฐ๋ผ ํ•„์š”ํ•œ Date ๊ฐ์ฒด์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค.

  • ์ง€๋‚œ ๋‹ฌ ๋งˆ์ง€๋ง‰ ๋‚ : ๋‹ฌ๋ ฅ์— ์ง€๋‚œ ๋‹ฌ์„ ๊ทธ๋ฆด ๋•Œ ์ผ์š”์ผ์„ ํ•˜์ด๋ผ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค.
  • ์ด๋ฒˆ ๋‹ฌ ์ฒซ ๋‚ : ์ด๋ฒˆ ๋‹ฌ์˜ ํ† ์š”์ผ๊ณผ ์ผ์š”์ผ์˜ ํŒŒ์•…ํ•˜์—ฌ ํ•˜์ด๋ผ์ดํŠธ ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค. ๋˜ํ•œ ์ด๋ฒˆ ๋‹ฌ ์ฒซ ๋‚ ์˜ ์š”์ผ์„ ํ†ตํ•ด ์ง€๋‚œ ๋‹ฌ ๋งˆ์ง€๋ง‰ ์ฃผ๋ฅผ ๋‹ฌ๋ ฅ์— ํ‘œ์‹œํ•  ๋•Œ ํ•„์š”ํ•˜๋‹ค.
  • ์ด๋ฒˆ ๋‹ฌ ๋งˆ์ง€๋ง‰ ๋‚ : ์ด๋ฒˆ ๋‹ฌ์˜ ๋‹ฌ๋ ฅ์„ for statement๋กœ ๊ทธ๋ฆด ๋•Œ ํ•„์š”ํ•˜๋‹ค.
  • ๋‹ค์Œ ๋‹ฌ ์ฒซ ๋‚ : ๋‹ฌ๋ ฅ์— ๋‹ค์Œ๋‹ฌ์„ ๊ทธ๋ฆด ๋•Œ ํ† ์š”์ผ์„ ํ•˜์ด๋ผ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค.

์ด 4๊ฐœ์˜ ๋‚ ์งœ๋ฅผ object๋กœ ๋ฌถ์–ด returnํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. argument๋กœ Date ๊ฐ์ฒด 1๊ฐœ๋ฅผ ๋ฐ›์œผ๋ฉฐ ์ด ๋‹ฌ๋ ฅ์—์„œ๋Š” "์˜ค๋Š˜"์— ํ•ด๋‹นํ•˜๋Š” Date ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ค„ ๊ฒƒ์ด๋‹ค.

const processDate = (day) => {
  const date = day.getDate()
  const month = day.getMonth()
  const year = day.getFullYear()
  return {
    lastMonthLastDate: new Date(year, month, 0),
    thisMonthFirstDate: new Date(year, month, 1),
    thisMonthLastDate: new Date(year, month + 1, 0),
    nextMonthFirstDate: new Date(year, month + 1, 1),
  }
}

2-2. getCalendarHTML ๋งŒ๋“ค๊ธฐ

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋‹ฌ๋ ฅ์„ ๊ทธ๋ ค๋ณด์ž. ๋‹ฌ๋ ฅ์— ๋“ค์–ด๊ฐˆ ๋‚ด์šฉ์„ HTML๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” getCalendarHTML ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. getCalendarHTML ํ•จ์ˆ˜๋Š” ์กฐ๊ธˆ ๊ฑฐ๋Œ€ํ•ด์„œ ๋จผ์ € ํ‹€์„ ์žก์•„์ฃผ์—ˆ๋‹ค.

const getCalendarHTML = () => {
  let today = new Date()
  let { lastMonthLastDate, thisMonthFirstDate, thisMonthLastDate, nextMonthFirstDate } = processDate(today)
  let calendarContents = []

  // ...

  return calendarContents.join('')
}

๋งจ ์œ„์— ์š”์ผ์„ ํ‘œ์‹œํ•  ์ค„์„ ์ถ”๊ฐ€ํ•˜์ž. ์ฒ˜์Œ์— ์ถ”๊ฐ€ํ•œ const๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งค์ง๋„˜๋ฒ„๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.

for (let d = 0; d < NUMBER_OF_DAYS_IN_WEEK; d++) {
  calendarContents.push(html`<div class="${NAME_OF_DAYS[d]} calendar-cell">${NAME_OF_DAYS[d]}</div>`)
}

๊ทธ ๋‹ค์Œ ์ง€๋‚œ ๋‹ฌ์„ ๊ทธ๋ฆฌ์ž. ์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋ฒˆ ๋‹ฌ์˜ ์ฒซ ๋‚ ์ด ์ˆ˜์š”์ผ์ด๋ผ๋ฉด ์ผ์š”์ผ, ์›”์š”์ผ, ํ™”์š”์ผ์— ํ•ด๋‹นํ•˜๋Š” ์ง€๋‚œ ๋‹ฌ์„ ๊ทธ๋ ค์ฃผ๋Š” ์—ญํ• ์ด๋‹ค. ์ผ์š”์ผ์— ํ•ด๋‹นํ•˜๋Š” ๋‚ ์€ sun HTML Class๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

for (let d = 0; d < thisMonthFirstDate.getDay(); d++) {
  calendarContents.push(
    html`<div
      class="
          ${d % 7 === 0 ? 'sun' : ''}
          calendar-cell
          past-month
        "
    >
      ${lastMonthLastDate.getMonth() + 1}/${lastMonthLastDate.getDate() - thisMonthFirstDate.getDay() + d}
    </div>`
  )
}

๋น„์Šทํ•œ ์›๋ฆฌ๋กœ ์ด๋ฒˆ ๋‹ฌ์„ ๊ทธ๋ฆฌ์ž. ์˜ค๋Š˜์— ํ•ด๋‹นํ•˜๋Š” ๋‚ ์€ today HTML Class์™€ " today" String์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ† ์š”์ผ๊ณผ ์ผ์š”์ผ์—๋Š” ๊ฐ๊ฐ sat๊ณผ sun HTML Class๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

for (let d = 0; d < thisMonthLastDate.getDate(); d++) {
  calendarContents.push(
    html`<div
      class="
          ${today.getDate() === d + 1 ? 'today' : ''}
          ${(thisMonthFirstDate.getDay() + d) % 7 === 0 ? 'sun' : ''}
          ${(thisMonthFirstDate.getDay() + d) % 7 === 6 ? 'sat' : ''}
          calendar-cell
          this-month
        "
    >
      ${d + 1} ${today.getDate() === d + 1 ? ' today' : ''}
    </div>`
  )
}

๋งˆ์ง€๋ง‰์œผ๋กœ ๋‚จ์€ ์นธ๋“ค์— ๋‹ค์Œ ๋‹ฌ์˜ ๋‚ ์งœ๋“ค์„ ๊ทธ๋ ค์ค€๋‹ค.

let nextMonthDaysToRender = 7 - (calendarContents.length % 7)

for (let d = 0; d < nextMonthDaysToRender; d++) {
  calendarContents.push(
    html`<div
      class="
          ${(nextMonthFirstDate.getDay() + d) % 7 === 6 ? 'sat' : ''}
          calendar-cell
          next-month
        "
    >
      ${nextMonthFirstDate.getMonth() + 1}/${d + 1}
    </div>`
  )
}

3. CSS ์ž‘์„ฑํ•˜๊ธฐ

3-1. display: grid ์ด์šฉํ•˜๊ธฐ

ํ•˜๋‚˜์˜ element์— display: grid๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ ์ž์‹ element๋“ค์„ ๊ทธ๋ฆฌ๋“œ(ํ‘œ) ์•ˆ์— ๊น”๋”ํ•˜๊ฒŒ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค.

  • grid-template-columns: column์„ ์–ด๋–ป๊ฒŒ ๋ฐฐ์น˜์‹œํ‚ฌ์ง€์— ๋Œ€ํ•œ ์ •๋ณด. 1fr์€ 1 fraction์ด๋ผ๋Š” ๋œป์œผ๋กœ ์—ฌ๊ธฐ์„œ๋Š” ์ด 7๋ฒˆ ์ž‘์„ฑํ–ˆ์œผ๋‹ˆ ๋„ˆ๋น„๊ฐ€ ๋™์ผํ•œ 7๊ฐœ์˜ column์ด ์ƒ์„ฑ๋œ๋‹ค.
  • grid-template-rows: row์˜ ํฌ๊ธฐ๋ฅผ ์ •์˜ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” 3rem ํ•˜๋‚˜๋งŒ ์žˆ์œผ๋‹ˆ ์ฒซ๋ฒˆ์งธ row๋ฅผ 3rem์ด๋ผ๊ณ  ์ •์˜ํ•œ ๊ฒƒ์ด๋‹ค.
  • grid-auto-rows: ์ดํ›„์˜ row๋ฅผ ํฌ๊ธฐ๋ฅผ ์ •์˜ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” 6rem์ด๋ผ๊ณ  ๋˜์–ด ์žˆ์œผ๋‹ˆ ์ดํ›„์˜ ๋ชจ๋“  row๋Š” row ํฌ๊ธฐ๊ฐ€ 6rem์ธ ๊ฒƒ์ด๋‹ค.

์•„๋ž˜์—๋Š” ์ถ”๊ฐ€์ ์ธ ์Šคํƒ€์ผ์„ ์ •์˜ํ•ด์ฃผ์—ˆ๋‹ค.

#App {
  /* grid */
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  grid-template-rows: 3rem;
  grid-auto-rows: 6rem;

  /* style */
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  border: 1px solid black;
  max-width: 720px;
  margin-left: auto;
  margin-right: auto;
}
  • ํ‘œ ๋“ฑ์„ ๊ทธ๋ฆด ๋•Œ ๋งˆ์น˜ ์—‘์…€์ฒ˜๋Ÿผ ๋ชจ๋“  ์นธ์„ ๊ท ์ผํ•œ ํ…Œ๋‘๋ฆฌ๋กœ ๊ฐ์‹ธ๊ณ  ์‹ถ์€๋ฐ ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ์˜ ์…€๋“ค๋งŒ ์„ ์ด ์–‡์€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. HTML๋กœ ๋”ฐ์ง€์ž๋ฉด th์™€ td์—๋งŒ border๋ฅผ ์ ์šฉํ•œ ์ƒํƒœ๋‹ค.
  • ๋‚˜๋Š” ์ด๊ฒƒ์„ "๋ชจ๋“  ์…€๋“ค ํ…Œ๋‘๋ฆฌ์— n px, ํ‘œ ํ…Œ๋‘๋ฆฌ์— n px" border๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ „์ฒด์ ์œผ๋กœ 2n px์˜ ๊ท ์ผํ•œ ํ…Œ๋‘๋ฆฌ๊ฐ€ ์ƒ๊ธด๋‹ค.
.calendar-cell {
  border: 1px solid black; /* #App์— ์ ์šฉํ•œ border์™€ ํ•จ๊ป˜ 2px์˜ ๊ท ์ผํ•œ ํ…Œ๋‘๋ฆฌ๊ฐ€ ์ƒ๊ธด๋‹ค.*/
  padding: 0.5rem;
}

3-2. ํ† ์š”์ผ๊ณผ ์ผ์š”์ผ, ์˜ค๋Š˜ ํ•˜์ด๋ผ์ดํŒ…

.past-month,
.next-month {
  color: gray;
}

.sun {
  color: red;
}

.sat {
  color: blue;
}

.past-month.sun {
  color: pink;
}

.next-month.sat {
  color: lightblue;
}

.today {
  color: #e5732f;
}

๋А๋‚€ ์ 

  • ์ฒ˜์Œ์— JS์™€ ์—ฐ๊ฒฐํ•˜์—ฌ ๋‹ฌ๋ ฅ์„ "initialize"ํ•  ๋•Œ ์‚ด์ง ํ—ค๋งธ๋‹ค. renderCalendar๋ฅผ body ์ƒ๋‹จ์— ์—ฐ๊ฒฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. DOM์€ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋‹จ์— ์—ฐ๊ฒฐํ•  ๊ฒฝ์šฐ #App div๊ฐ€ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์•˜์„ ๋•Œ renderCalendar๊ฐ€ ์‹คํ–‰๋˜์–ด DOM element๋ฅผ ์ฐพ์ง€ ๋ชปํ•œ๋‹ค.
  • ๋˜ JS์˜ ์—ฐ๊ด€ ๊ด€๊ณ„๋กœ ํ‘œํ˜„๋  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋“ค์„ ์–ด๋–ป๊ฒŒ ํ™”๋ฉด์— ๋ Œ๋”๋งํ•˜๋Š”์ง€ ๊ธฐ์–ต์ด ์ž˜ ๋‚˜์ง€ ์•Š์•˜๋‹ค. ๋‹จ์ˆœํžˆ ๊ฐ€์žฅ ์ฒซ index.js ์—ญํ• ์„ ํ•˜๋Š” js์—์„œ ์•ฑ์„ querySelectํ•œ ํ›„ innerHTML๋กœ ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.
  • ์šฐ์•„ํ•œํ…Œํฌ์บ ํ”„ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋งค์ง ๋„˜๋ฒ„๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค. ์ด๋ฒˆ์—๋Š” ๋งค์ง ๋„˜๋ฒ„๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ๊ฐ€๋…์„ฑ์„ ํ–ฅ์ƒ์‹œ์ผฐ๋‹ค.
  • ์šฐ์•„ํ•œํ…Œํฌ์บ ํ”„ ํ”„๋กœ์ ํŠธ๋Š” Object Orientedํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ(๋” ์ •ํ™•ํ•˜๊ฒŒ๋Š” Singleton ํŒจํ„ด)๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์—๋Š” ์ž‘์€ ํ•จ์ˆ˜๋“ค๋กœ ์ชผ๊ฐœ์„œ ์ž‘์„ฑํ–ˆ๋‹ค.
  • ES6+ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ–ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐฑํ‹ฑ์— ๋ณ€์ˆ˜๋ฅผ ๋„ฃ๊ฑฐ๋‚˜ processDate์˜ ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ๋ฅผ destructuringํ•ด์„œ ์‚ฌ์šฉํ–ˆ๋‹ค. ๋˜ let๊ณผ const๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ–ˆ๋‹ค.
  • getCalendarHTML๋ฅผ ์กฐ๊ธˆ ๋” ์งง๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์—†์—ˆ๋Š”์ง€ ์•„์‰ฌ์›€์ด ๋‚จ๋Š”๋‹ค.