๐Ÿ”จ ํ”„๋กœ์ ํŠธ ์ผ์ง€

์ •๋ณด ์ „๋‹ฌ๋ณด๋‹จ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ๊ฒช์€ ์ ๋“ค, ๋Š๋‚€ ์ ๋“ค์„ ๊ธฐ๋กํ•œ ์ผ์ง€

Project: Get Shit Done

๋‚˜๋งŒ์˜ ToDo๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์‹œ์ž‘ํ•œ ํ”„๋กœ์ ํŠธ
Get Shit Done์€ ๊ทธ๋ƒฅ ๋‹ฅ์น˜๊ณ  ํ•ด๋ผ๋Š” ์˜๋ฏธ๋กœ ํ‰์ƒ์‹œ ์Šค์Šค๋กœ ๋‹ค์งํ•˜๋Š” ๋ฌธ์žฅ์ธ๋ฐ, ํˆฌ๋‘๋ฆฌ์ŠคํŠธ์™€ ์–ด์šธ๋ฆฐ๋‹ค ์ƒ๊ฐํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ช…์œผ๋กœ ์ •ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์žฅ ํฐ ๋ชฉ์ ๊ณผ ์˜์˜๋Š” ์›นํŒฉ ๋ฐ ๋ฐ”๋ฒจ์„ ํ†ตํ•ด ๊ฐ ๋ชจ๋“ˆ์„ ์›น ์ปดํฌ๋„ŒํŠธ๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค!!!

๐Ÿคฎ drag & drop

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

์ด ๋ชจ๋“  ์–ด๋ ค์›€์˜ ์›ํ‰์€ Shadow DOM ์ž์‹์ด์—ˆใ„ทโ€ฆ(๋ฌผ๋ก  ์ปดํฌ๋„ŒํŠธ๋ฅผ ์–ผ๋งˆ๋‚˜ ์ž˜ ๋‚˜๋ˆ„๊ณ  ์—ญํ• ์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฒŒ ์†Œ์ค‘ํ•œ์ง€ ์•Œ ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ ใ… )

๋จผ์ € ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋ž API๋ฅผ ํ†ตํ•ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ• ๋ž˜๋„, ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋…๋ฆฝ์ ์ธ DOM์„ ๊ฐ€์ ธ๋ฒ„๋ฆฌ๋Š” ๋ฐ”๋žŒ์— drag ํ˜น์€ drop ์ด๋ฒคํŠธ ์‹œ ๋“œ๋ž˜๊ทธํ•œ ํƒ€๊ฒŸ๊ณผ ๋“œ๋ž๋  ๊ณต๊ฐ„์„ ํƒ€๊ฒŸํ•˜๋Š” ๊ฒŒ ๋ฒˆ๊ฑฐ๋กœ์› ๋‹ค.

๐ŸŒถ๏ธ ๋งค์šฐ๋งค์šฐ ์ง€์ €๋ถ„ํ•œ ์ฝ”๋“œ๋“ค์ด ๋“ฑ์žฅํ•˜๋‹ˆ ์ฃผ์˜

1. item(memo)๊ฐ€ ์žˆ๋Š” ๋ฉ”๋ชจ์žฅ์œผ๋กœ drag & drop
// 1) ์•„์ดํ…œ ์ฆ‰, ๋ฉ”๋ชจ๊ฐ€ ์กด์žฌํ•˜๋Š” ๋ฉ”๋ชจ์žฅ๋ผ๋ฆฌ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋ž
// todoItem.js
this.setAttribute('draggable', 'true');

// ๋“œ๋ž˜๊ทธ ์‹œ์ž‘ ์‹œ
this.addEventListener('dragstart', (event) => {
  const containerTitle = event.target.dataset.containerTitle;
  const $todoApp = document.querySelector('todo-app');
  const $targetContainer = $todoApp.shadowRoot.querySelector(
    `[data-container-title=${containerTitle}]`,
  );
  const $targetList = $targetContainer.shadowRoot.querySelector('todo-list');
  // โญ already ํด๋ž˜์Šค๋Š” ์™œ ์žˆ์„๊นŒ? ์•„๋ž˜์—์„œ ๊ทธ ์ด์œ ๊ฐ€ ๋‚˜์˜จ๋‹ค!!
  event.target.classList.add('drag-target', 'already');
  $targetList.classList.add('drag-target');
  $targetContainer.classList.add('drag-target');
});

๋ฉ”๋ชจ์— draggable = true๋ฅผ ๋ถ€์—ฌํ•˜๊ณ  shadow dom์œผ๋กœ ์ธํ•ด ์ถ”ํ›„ drop ์‹œ ํ˜„์žฌ event.target์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ธฐ์— dragstart ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ˆœ๊ฐ„, ํ•ด๋‹น item ๋ฐ ์ƒ์œ„ ์š”์†Œ๋“ค(Item, List, Container)์— drag-target์ด๋ผ๋Š” ํด๋ž™์Šค๋ฅผ ๋”ํ•ด์ค€๋‹ค.

this.addEventListener('drop', (event) => {
  event.preventDefault();
  event.stopPropagation();
  // dragstart ๋ฐœ์ƒ ์‹œ, ๋”ํ•ด์ค€ drag-target์œผ๋กœ drag์ค‘์ธ ์š”์†Œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  const $todoApp = document.querySelector('todo-app');
  const $targetContainer = $todoApp.shadowRoot.querySelector('.drag-target');
  const $targetList = $targetContainer.shadowRoot.querySelector('.drag-target');
  const $targetItem = $targetList.shadowRoot.querySelector('.drag-target');
  // ๋“œ๋ž˜๊ทธ ํ–ˆ์œผ๋‹ˆ ์ง€์šด๋‹ค.
  $targetItem.remove();

  // ์ด ์•„๋ž˜๋Š” ๋“œ๋žํ•˜๊ฒŒ ๋  Item(๋ฉ”๋ชจ ์š”์†Œ)์˜ ์ ˆ๋ฐ˜ ์ค‘์•™ ์œ—๋ถ€๋ถ„์ด๋ฉด ์œ„๋กœ, ์•„๋žซ๋ถ€๋ถ„์ด๋ฉด ์•„๋ž˜๋กœ drop๋˜๋„๋ก
  // drop๋˜๋Š” ๋…ธ๋“œ์˜ ์ค‘์•™์˜ clientY์™€ drop ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์ง€์ ์˜ clientY๋ฅผ ๋น„๊ตํ•˜์—ฌ
  // ๊ตฌ๋ถ„ํ•ด์„œ ๋“œ๋ž์„ ํ•ด์ฃผ์—ˆ๋‹ค.
  const dropTargetClientTop = event.target.getBoundingClientRect().top;
  const dropTargetClientBottom = event.target.getBoundingClientRect().bottom;
  const dropTargetClientMiddle = (dropTargetClientTop + dropTargetClientBottom) / 2;
  if (event.clientY < dropTargetClientMiddle) {
    event.target.parentNode.insertBefore($targetItem, event.target);
  } else {
    event.target.parentNode.insertBefore($targetItem, event.target.nextElementSibling);
  }
  $targetItem.dataset.containerTitle = event.target.dataset.containerTitle;

  // drag & drop ํ›„ ๊ฐ๊ฐ์˜ ๋ฉ”๋ชจ์žฅ์—์„œ -1, +1 count ํ•ด์ฃผ๊ธฐ
  const $targetToolbar = $targetContainer.shadowRoot.querySelector('todo-toolbar');
  const $noteCountBefore = $targetToolbar.shadowRoot.querySelector('.count-item');
  $noteCountBefore.textContent = +$noteCountBefore.textContent - 1;

  const $noteCountAfter = document
    .querySelector('todo-app')
    .shadowRoot.querySelector(
      `[
            data-container-title=${event.target.dataset.containerTitle}
          ]`,
    )
    .shadowRoot.querySelector('todo-toolbar')
    .shadowRoot.querySelector('.count-item');
  $noteCountAfter.textContent = +$noteCountAfter.textContent + 1;

  // drag & drop ๊ธฐ๋ก ๋‚จ๊ฒจ์ฃผ๊ธฐ
  const $record = document.createElement('div');
  $record.className = 'record';
  $record.innerHTML = `<span class="record-important">@Jayden</span> moved <span class="record-item">${$targetItem.dataset.itemTitle}</span> to <span class="record-container">${event.target.dataset.containerTitle}</span> from <span class="record-container">${$targetContainer.dataset.containerTitle}</span>`;

  const now = Date.now();
  $record.dataset.timeMakeNote = now;
  activityLog.push($record);

  // drop ํ›„ drag target์— ์ถ”๊ฐ€ํ–ˆ๋˜ ํด๋ž˜์Šค๋Š” ์ง€์›Œ์ค€๋‹ค.
  $targetContainer.classList.remove('drag-target');
  $targetList.classList.remove('drag-target');
  $targetItem.classList.remove('drag-target');
});

์ผ๋‹จ drag ๋Œ€์ƒ๊ณผ drop ๋Œ€์ƒ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒŒ ๋งค์šฐ ์ง€์ €๋ถ„ํ•˜์ง€๋งŒ, ๋ฉ”๋ชจ์žฅ์— ๋ฉ”๋ชจ๋“ค์ด ์กด์žฌํ•  ๋•Œ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋ž์„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

ํ—Œ์ œ, ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. drag & drop ํ›„ ์˜ฎ๊ฒจ์ง„ ๋ฉ”๋ชจ๋ฅผ ์‚ญ์ œ, ์ˆ˜์ • ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•ด๋ณด๋‹ˆ ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ 2๋ฒˆ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
์ฒ˜์Œ์— ๋ญ๊ฐ€ ๋ฌธ์ œ์ง€..? ํ•˜๋‹ค๊ฐ€ ์•Œ๊ณ ๋ณด๋‹ˆ drag ํ›„ dropํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น todo-item ํƒœ๊ทธ๊ฐ€ ์ง€์›Œ์กŒ๋‹ค๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—
์›น ์ปดํฌ๋„ŒํŠธ์—์„œ customElement๊ฐ€ DOM์— ์ถ”๊ฐ€๋  ๋•Œ ์‹คํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ์ธ connectedCallback์ด ์‹คํ–‰๋˜๊ฒŒ ๋˜๊ณ  ๊ทธ์— ๋”ฐ๋ผ, addEventListener๊ฐ€ ํ•œ ๋ฒˆ ๋” ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•  ์ง๊ด€์ ์ธ(?) ๋‹จ์ˆœํ•œ ๋ฐฉ๋ฒ•์€ disconnectedCallback์—์„œ removeEventListener()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.
ํ—ˆ๋‚˜, ์ด์ „ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ์š”์†Œ์— ๋ถ™์ธ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋“ค์ด ๋„ˆ๋ฌด ์ง€์ €๋ถ„ํ•˜๊ณ  ๋ณต์žกํ–ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‹ค ๋ฒˆ๋œฉ ๋– ์˜ค๋ฅธ ์•„์ด๋””์–ด! drag & drop์„ ํ•œ ์š”์†Œ์— ๋Œ€ํ•ด์„œ๋งŒ ๋ฐœ์ƒํ•˜๋Š” ํ˜„์ƒ์ด๋‹ˆ, ํ•ด๋‹น ์š”์†Œ๋“ค์„ ๊ตฌ๋ถ„ํ•˜๊ณ  connectedCallback์— early return์„ ์ฃผ๋ฉด ์–ด๋–จ๊นŒ? ๋ฐ”๋กœ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

// dragstart ์ด๋ฒคํŠธ ์‹œ already๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ๊ฐ™์ด ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.
event.target.classList.add('drag-target', 'already');

connectedCallback() {
    // drag & drop๋œ ํƒœ๊ทธ๋Š” .already๊ฐ€ ์กด์žฌํ•˜๊ธฐ์— ๋ฐ”๋กœ return๋œ๋‹ค.
    if (this.className) return;
}

ํฌ์œผโ€ฆ ์กฐ์žกํ•˜์ง€๋งŒ ๋ญ”๊ฐ€ ์ •๋ง ๊น”๋”ํ•˜๊ฒŒ ์ฝ”๋“œ ๋ช‡์ค„๋กœ ํ•ด๊ฒฐํ•ด์„œ ๊ธฐ๋ถ„์ด ์ข‹์•˜๋‹คใ…‹ใ…‹ใ…‹ใ…‹ใ…‹

2. item(memo)๊ฐ€ ์—†๋Š” ๋นˆ ๋ฉ”๋ชจ์žฅ์œผ๋กœ drag & drop
// todoList.js
connectedCallback() {
    this.addEventListener('dragover', (event) => {
      event.preventDefault();
    });

    this.addEventListener('drop', (event) => {
      event.preventDefault();
      const $todoApp = document.querySelector('todo-app');
      const $targetContainer =
        $todoApp.shadowRoot.querySelector('.drag-target');
      const $targetList =
        $targetContainer.shadowRoot.querySelector('todo-list');
      const $targetItem = $targetList.shadowRoot.querySelector('.drag-target');
      $targetItem.remove();
      event.target.shadowRoot.appendChild($targetItem);
      $targetItem.dataset.containerTitle = event.target.dataset.containerTitle;

      const $targetToolbar =
        $targetContainer.shadowRoot.querySelector('todo-toolbar');
      const $noteCountBefore =
        $targetToolbar.shadowRoot.querySelector('.count-item');
      $noteCountBefore.textContent = +$noteCountBefore.textContent - 1;

      const $noteCountAfter =
        event.target.previousElementSibling.previousElementSibling.shadowRoot.querySelector(
          '.count-item'
        );
      $noteCountAfter.textContent = +$noteCountAfter.textContent + 1;

      const $record = document.createElement('div');
      $record.className = 'record';
      $record.innerHTML = `<span class="record-important">@Jayden</span> moved <span class="record-item">${$targetItem.dataset.itemTitle}</span> to <span class="record-container">${event.target.dataset.containerTitle}</span> from <span class="record-container">${$targetContainer.dataset.containerTitle}</span>`;

      const now = Date.now();
      $record.dataset.timeMakeNote = now;
      activityLog.push($record);

      $targetContainer.classList.remove('drag-target');
      $targetList.classList.remove('drag-target');
      $targetItem.classList.remove('drag-target');
    });
  }

์ด๋ฒˆ์—” ๋นˆ ๊ณต๊ฐ„(Item๋“ค์ด ๋“ค์–ด๊ฐˆ List)์— drop ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋ถ™์ด๊ณ  ํƒ€๊ฒŸ์„ ์ฐพ๋Š” ๊ฒฝ๋กœ(?)๋งŒ ์กฐ๊ธˆ ๋‹ฌ๋ ค์กŒ์„ ๋ฟ ์œ„์™€ ๊ฑฐ์˜ ๋กœ์ง์€ ๋™์ผํ•˜๋‹ค!


๐Ÿ”š ํ”„๋กœ์ ํŠธ ํšŒ๊ณ 

๊ทธ๋™์•ˆ html, css, js๋กœ๋งŒ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ค„์ง„ ํ”„๋กœ์ ํŠธ๋งŒ ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ ๋ชจ๋“ˆ, ์ปดํฌ๋„ŒํŠธ ๋“ฑ์˜ ๊ฐœ๋…์„ ๋„์ž…ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๋ˆˆ์„ ์–ป๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™๋‹ค.
์™œ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๋Š”์ง€, ์ปดํฌ๋„ŒํŠธ๋Š” ๋ญ๊ณ  ์›น ์ปดํฌ๋„ŒํŠธ๋Š” ์™œ ์กด์žฌํ•˜๋Š”์ง€๋ถ€ํ„ฐ ๋” ๋‚˜์•„๊ฐ€์„œ ์ด๋ ‡๊ฒŒ ๊ธฐ๋Šฅ ๋ณ„ ํ˜น์€ UI์— ๋”ฐ๋ผ ๊ตฌ๋ถ„์ง€์–ด์„œ ์ œํ’ˆ์„ ๋งŒ๋“œ๋Š” ๊ฒŒ ๋ฌด์Šจ ์˜๋ฏธ๊ฐ€ ์žˆ๋Š”์ง€์— ๋Œ€ํ•ด์„œ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๊ฐ์ฒด์ง€ํ–ฅ์— ๋Œ€ํ•œ ๊ฐœ๋…๋„ ์ฐพ์•„๋ณด๊ฒŒ ๋˜๊ณ  ์•„์ง์€ ์ •๋ฆฌํ•ด์•ผํ•  ๊ฒŒ ์‚ฐ๋”๋ฏธ์ง€๋งŒ ์ •๋ง ๊ทธ๋™์•ˆ์€ ๋ณผ ์ˆ˜ ์—†์—ˆ๋˜ ์„ธ์ƒ์„ ์•Œ๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™์•„์„œ ๋ฟŒ๋“ฏํ•˜๋‹ค.
๋˜, ์ฝ”๋“œ ์ž์ฒด๋Š” ๋„ˆ๋ฌด๋‚˜ ์ง€์ €๋ถ„ํ•˜์ง€๋งŒ ๋ชฉํ‘œํ–ˆ๋˜ ๊ธฐ๋Šฅ๋“ค์„ ๋๊นŒ์ง€ ๊ตฌํ˜„ํ–ˆ๋‹ค๋Š” ์ ์—์„œ ๋‚˜ ์ž์‹ ์„ ์นญ์ฐฌํ•ด์ฃผ๊ณ  ์‹ถ๋‹ค.
๋ฐฐ์šธ ๊ฒŒ ๋งŽ๋‹ค๋Š” ๊ฑด ๋•Œ๋กœ๋Š” ์ŠคํŠธ๋ ˆ์Šค์ด๊ณ  ๊ณ ํ†ต์ผ ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ, ๋‚˜์•„๊ฐˆ ๊ฒƒ์ด ๋งŽ๋‹ค๋Š” ์˜๋ฏธ์ด๊ธฐ์— ์„ค๋ ˆ๋Š” ์ผ์ด๊ธฐ๋„ ํ•œ ๊ฒƒ ๊ฐ™๋‹ค.
ํ”ํžˆ ๋„์„œ๋‚˜ ์˜ํ™”, ์›นํˆฐ ๋“ฑ์—์„œ ๋ช…์ž‘์„ ์•„์ง ์•ˆ๋ณธ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์™€... ๋„ˆ๋ฌด ๋ถ€๋Ÿฝ๋‹ค. ๊ทธ ๋ช…์ž‘ ์•ˆ๋ณธ ๋‡Œ ์‚ฌ๊ณ  ์‹ถ๋‹ค.๋ผ๋Š” ํ‘œํ˜„์„ ์“ด๋‹ค.
๋•Œ๋กœ๋Š” ์•„์ง ๋ชจ๋ฅด๋Š” ๊ฒŒ ๋„ˆ๋ฌด๋‚˜ ๋งŽ์•„ ์กฐ๋ฐ”์‹ฌ์ด ๋‚˜์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ๋ฐฐ์šธ ๊ฒŒ ๋งŽ์€ ์ง€๊ธˆ์ด ์ฐธ ์†Œ์ค‘ํ•˜๊ณ  ์ด๋Ÿฐ ์ƒ๊ฐ์„ ํ•˜๊ณ  ์žˆ๋Š” ๋‚ด ์ž์‹ ์„ ๋” ์‘์›ํ•˜๊ณ  ์‹ถ๋‹ค.