๐Ÿ’ก TodoList์— ๋‹คํฌ๋ชจ๋“œ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ •๋™ํ™˜ยท2023๋…„ 6์›” 22์ผ
4
post-thumbnail

1. ๊ฐœ์š”

์ด๋ฒˆ์ฃผ์— ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ๋ฐ๋ธŒ์ฝ”์Šค์—์„œ Vanilla Javascript๋ฅผ ์ด์šฉํ•ด TodoList๋ฅผ ๋งŒ๋“œ๋Š” ๊ณผ์ œ๋ฅผ ์ง„ํ–‰ํ–ˆ๋‹ค.

๋‚˜๋Š” ๋‹คํฌ๋ชจ๋“œ ๋งŒ๋“ค๊ธฐ๋ฅผ ์ถ”๊ฐ€ ๋ชฉํ‘œ๋กœ ์„ธ์› ๋‹ค.
์–ด๋–ค ์ปจ์…‰์œผ๋กœ ๊ตฌํ˜„ํ• ์ง€ ์†Œ์Šค๋ฅผ ์ฐพ์•„ ๋‹ค๋‹ˆ๋‹ค๊ฐ€

์ฟ ๋กœ๋ฏธ์™€ ๋งˆ์ด๋ฉœ๋กœ๋””๋ฅผ ์ด์šฉํ•ด ๋‹คํฌ๋ชจ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค

2. ๋กœ์ง

CSS Variable ์ด์šฉ

div#app ํƒœ๊ทธ์˜ 'dark' ํด๋ž˜์Šค ์กด์žฌ ์—ฌ๋ถ€์— ๋”ฐ๋ผ css๋ณ€์ˆ˜ ๊ฐ’์ด ๊ฒฐ์ •๋˜๋ฉฐ ๋‹คํฌ ๋ชจ๋“œ๊ฐ€ ๋ฐ˜์˜๋œ๋‹ค

App์ด ์ฒ˜์Œ ์‹คํ–‰๋  ๋•Œ

App์ด ์‹คํ–‰๋˜๋ฉด localStorage์™€ ์‚ฌ์šฉ์ž ์„ธํŒ…์„ ํ™•์ธํ•œ๋‹ค

  • localStorage์— theme๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์œผ๋ฉด theme์— ๋”ฐ๋ผ ๋ชจ๋“œ ์ดˆ๊ธฐํ™”
  • localStorage์— theme๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด ์‚ฌ์šฉ์ž ์„ธํŒ…์— ๋งž์ถฐ์„œ ๋ชจ๋“œ ์ดˆ๊ธฐํ™”

์‚ฌ์šฉ์ž๊ฐ€ theme ๋ณ€๊ฒฝ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ

  1. App ๋‚ด๋ถ€์—์„œ ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” theme ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค
  2. App์˜ ํƒ€๊ฒŸ์ธ divํƒœ๊ทธ์— theme ์—…๋ฐ์ดํŠธ๋ฅผ ๋ฐ˜์˜ํ•œ๋‹ค
    • ๋‹คํฌ๋ชจ๋“œ๋ผ๋ฉด div์— dark ํด๋ž˜์Šค ์ถ”๊ฐ€
    • ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด div์—์„œ dark ํด๋ž˜์Šค ์ œ๊ฑฐ
  3. ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€์— ๋ณ€๊ฒฝ๋œ ์‚ฌํ•ญ์„ ์ €์žฅํ•œ๋‹ค.

3. ๊ตฌํ˜„

1. CSS ์„ธํŒ…

์‚ฌ์šฉํ•  ์ƒ‰๊น”๋“ค ๋ณ€์ˆ˜๋กœ ์„ ์–ธ

:root {
  --main-black: #333235;
  --main-white: #efeef1;
  --main-pupple: #9c93d1;
  --main-pink: #f2a6be;
  --main-background: var(--main-white);
  --main-text-color: var(--main-pink);
  --main-delete-color: var(--main-pupple);
}

์šฐ์„  ์ตœ์ƒ๋‹จ ๋ฃจํŠธ์— ์‚ฌ์šฉํ•  ์ƒ‰๊น”๋“ค์„ CSS๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•ด ์ •์˜ํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์ด์šฉํ•ด ๋ผ์ดํŠธ ๋ชจ๋“œ์ผ๋•Œ ๋ฐฐ๊ฒฝ์ƒ‰, ํ…์ŠคํŠธ, ๊ทธ๋ฆฌ๊ณ  ์‚ญ์ œ ๋ฒ„ํŠผ์˜ ์ƒ‰์„ ์ •์˜ํ–ˆ๋‹ค.

  • ๋ผ์ดํŠธ๋ชจ๋“œ๋Š” ๋งˆ์ด๋ฉœ๋กœ๋””๋ฅผ ์ƒ์ง•ํ•œ๋‹ค
  • ์‚ญ์ œ๋ฒ„ํŠผ์˜ ์ƒ‰๊น”์€ ์ฟ ๋กœ๋ฏธ๋ฅผ ์ƒ์ง•ํ•œ๋‹ค

๋‹คํฌ๋ชจ๋“œ์ผ๋•Œ ๋ณ€์ˆ˜ ๊ฐ’ ๋ณ€๊ฒฝ

#app.dark {
  --main-background: var(--main-black);
  --main-text-color: var(--main-pupple);
  --main-delete-color: var(--main-pink);
}

๋‹คํฌ ๋ชจ๋“œ์ผ๋•Œ ๋ฐฐ๊ฒฝ์ƒ‰, ํ…์ŠคํŠธ, ์‚ญ์ œ ๋ฒ„ํŠผ์˜ ๋ณ€์ˆ˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค.

  • ๋‹คํฌ๋ชจ๋“œ๋Š” ์ฟ ๋กœ๋ฏธ๋ฅผ ์ƒ์ง•ํ•œ๋‹ค
  • ์‚ญ์ œ๋ฒ„ํŠผ์˜ ์ƒ‰๊น”์€ ๋งˆ์ด๋ฉœ๋กœ๋””๋ฅผ ์ƒ์ง•ํ•œ๋‹ค

์›น ์•„์ด์ฝ˜ ์ค€๋น„

๋„ค๊ฐ€์ง€ ์•„์ด์ฝ˜์„ ์ค€๋น„ํ–ˆ๋‹ค

  • ํ† ๊ธ€๋ฒ„ํŠผ์„ ์œ„ํ•œ ํ•ด์™€ ๋‹ฌ ์•„์ด์ฝ˜
  • ์ฟ ๋กœ๋ฏธ๋ฅผ ์ƒ์ง•ํ•˜๋Š” ๊ณ ์–‘์ด ์•„์ด์ฝ˜
  • ๋งˆ์ด๋ฉœ๋กœ๋””๋ฅผ ์ƒ์ง•ํ•˜๋Š” ํ† ๋ผ ์•„์ด์ฝ˜

์œ„์˜ ์„ธ๊ฐ€์ง€๋Š” fontawesome์—์„œ ๊ฐ€์ ธ์™”๊ณ  ํ† ๋ผ ์•„์ด์ฝ˜๋งŒ material icons์—์„œ ๊ฐ€์ ธ์™”๋‹ค.

ํ† ๋ผ ์•„์ด์ฝ˜์„ ์ฐพ๋Š”๊ฒŒ ์ •๋ง ํž˜๋“ค์—ˆ๋‹ค... ๊ฑฐ์˜ ์„ธ์‹œ๊ฐ„๋™์•ˆ ์ฐพ์•˜๋‹ค.

์›น ์•„์ด์ฝ˜์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด HTML head์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•œ๋‹ค


<!--fontawesome, material icons-->
<link href="https://fonts.googleapis.com/css2?family=Material+Icons"
      rel="stylesheet" />
    <script src=[YOUR_KIT] crossorigin="anonymous"></script>

<!--๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ css-->
<link rel="stylesheet" href="style.css" />

โ—์œ„์˜ ์ฝ”๋“œ๋ฅผ ์ž„ํฌํŠธํ• ๋•Œ style.css์•„๋ž˜์— ์ž‘์„ฑํ•ด์•ผ ๋‚˜์ค‘์— material-icon์„ ์ˆ˜์ •ํ• ๋•Œ ํŽธ๋ฆฌํ•˜๋‹ค.

์ˆœ์„œ๋ฅผ ๋ฐ˜๋Œ€๋กœํ•˜๋ฉด ๊ตฌ๊ธ€์—์„œ ์ œ๊ณตํ•œ material-icons ํด๋ž˜์Šค๊ฐ€ style.css์—์„œ ์ •์˜ํ•œ material-icons ํด๋ž˜์Šค ์Šคํƒ€์ผ์„ ๋ฎ์–ด์”Œ์›Œ ๋ฌด์‹œํ•œ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” style.css์—์„œ ๋” ์ž์„ธํ•˜๊ฒŒ ์„ ํƒ์ž๋ฅผ ํ‘œ๊ธฐํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. ์‚ฌ์‹ค ๋‚œ ์ด๋ ‡๊ฒŒ ํ–ˆ๋‹ค

.title .material-icons {
  font-size: 5rem;
}

CSS์— material icons๋ฅผ ์œ„ํ•œ ์„ธํŒ…์„ ์ถ”๊ฐ€ํ•œ๋‹ค

@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */
  src: local('Material Icons'), local('MaterialIcons-Regular'),
    url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'),
    url(https://example.com/MaterialIcons-Regular.woff) format('woff'),
    url(https://example.com/MaterialIcons-Regular.ttf) format('truetype');
}

์„ธํŒ…์„ ๋งˆ์น˜๋ฉด ์›ํ•˜๋Š” ๊ณณ์—์„œ ์›น ์•„์ด์ฝ˜ ์‚ฌ์šฉ๊ฐ€๋Šฅ!

<!--ํ•ด, ๋‹ฌ-->
<i class="fa-solid fa-moon"></i>
<i class="fa-solid fa-sun"></i>

<!--๊ณ ์–‘์ด, ํ† ๋ผ-->
<i class="fa-solid fa-cat"></i>
<span class="material-icons">
  cruelty_free</span>`;

2. ๋‹คํฌ๋ชจ๋“œ ๊ธฐ๋Šฅ ๊ตฌํ˜„

App.js

...
import store from './store/store.js';
export default class App extends Component{
  ...
  setup() {
	...
    const DarkTheme =
      store.theme === 'dark' ||
      (!store.theme &&
        window.matchMedia('(prefers-color-scheme: dark)').matches);

    this.setState({ todos: initialTodoList, theme: DarkTheme });
    this.updateDarkMode(DarkTheme);
  }

	...
  
  mounted() {
    const { todos, theme } = this.state;

    const $header = this.$target.querySelector(HEADER_SELECTOR);
    new Header({
      $target: $header,
      props: {
        addItem: this.addItem.bind(this),
        darkMode: theme,
        toggleDarkMode: this.toggleDarkMode.bind(this),
      },
    });

    ...
  }
  
  toggleDarkMode() {
  const { theme } = this.state;
    this.setState({ ...this.state, theme: !theme });
    this.updateDarkMode(!theme);
  }

  updateDarkMode(darkMode) {
    if (darkMode) {
      this.$target.classList.add('dark');
      store.theme = 'dark';
    } else {
      this.$target.classList.remove('dark');
      store.theme = 'light';
    }
  }
}

์œ„์—์„œ๋ถ€ํ„ฐ ์„ค๋ช…ํ•˜์ž๋ฉด

setup() {
	...
    const DarkTheme =
      store.theme === 'dark' ||
      (!store.theme &&
        window.matchMedia('(prefers-color-scheme: dark)').matches);

    this.setState({ todos: initialTodoList, theme: DarkTheme });
    this.updateDarkMode(DarkTheme);
  }

App์ด ์ฒ˜์Œ ์‹คํ–‰๋œ ํ›„ localStorage๋ฐ ์‚ฌ์šฉ์ž ์„ธํŒ…์„ ๊ฒ€์‚ฌํ•œ ํ›„ ์•Œ๋งž์€ theme๋ฅผ ์„ค์ •ํ•œ๋‹ค

์ด๋ ‡๊ฒŒ ๋‹คํฌ๋ชจ๋“œ๋ฅผ ํŒ๋‹จํ•˜๋Š” ๋กœ์ง์€ tailwindCSS์—์„œ ๊ฐ€์ ธ์˜ธ๋‹ค

const $header = this.$target.querySelector(HEADER_SELECTOR);
new Header({
      $target: $header,
      props: {
        addItem: this.addItem.bind(this),
        darkMode: theme,
        toggleDarkMode: this.toggleDarkMode.bind(this),
      },
    });

App์—์„œ Header๋ฅผ ์ƒ์„ฑํ• ๋•Œ ํ˜„์žฌ theme ๋ฐ ๋ณ€๊ฒฝ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์คฌ๋‹ค.

  toggleDarkMode() {
  const { theme } = this.state;
    this.setState({ ...this.state, theme: !theme });
    this.updateDarkMode(!theme);
  }

  updateDarkMode(darkMode) {
    if (darkMode) {
      this.$target.classList.add('dark');
      store.theme = 'dark';
    } else {
      this.$target.classList.remove('dark');
      store.theme = 'light';
    }
  }
  • toggleDarkMode๋Š” ๋‹คํฌ๋ชจ๋“œ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜๋‹ค. App์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” theme์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  updateDarkModeํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
  • updateDarkModeํ•จ์ˆ˜๋Š” DOM์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜๊ณ  localStorage์— ๋ณ€๊ฒฝ๋œ theme๋ฅผ ์ €์žฅํ•œ๋‹ค

Header.js

export default class Header extends Component {
  templates() {
    const { darkMode } = this.props;
    return `
      <h1 class='title'>
        ${this.getAnimalLogoTemplate(darkMode)}
        TodoList
        <button class='toggle-dark'>
        	${this.getDarkModeButtonTemplate(darkMode)}
        </button>
      </h1>
      ...
    `;
  }

  setEvent() {
    const { toggleDarkMode } = this.props;
    
    ...
    
    this.addEvent({
      eventType: CLICK,
      selector: TOGGLE_DARKMODE_SELECTOR,
      callback: toggleDarkMode,
    });
  }

  ...

  getDarkModeButtonTemplate(darkMode) {
    return darkMode
      ? `<i class="fa-solid fa-moon"></i>`
      : `<i class="fa-solid fa-sun"></i>`;
  }

  getAnimalLogoTemplate(darkMode) {
    return darkMode
      ? `<i class="fa-solid fa-cat"></i>`
      : `<span class="material-icons">
          cruelty_free
        </span>`;
  }
}
  • Header์—์„œ๋Š” ํ…Œ๋งˆ์— ๋”ฐ๋ผ ๋‹คํฌ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ์˜ ๋กœ๊ณ ์™€ ๋™๋ฌผ ๋กœ๊ณ ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ๋ Œ๋”๋งํ•ด์ค€๋‹ค.
  • ๋ผ์ดํŠธ ๋ชจ๋“œ๋ผ๋ฉด ํ•ด์™€ ํ† ๋ผ, ๋‹คํฌ ๋ชจ๋“œ๋ผ๋ฉด ๋‹ฌ๊ณผ ๊ณ ์–‘์ด๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
  • App์—์„œ ๋ฐ›์€ ๋ฆฌ์Šค๋„ˆ ํ•จ์ˆ˜๋ฅผ ํ† ๊ธ€ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ๋กœ ๋“ฑ๋กํ•œ๋‹ค.

Store.js

class Store {
  #storage;
  constructor(storage) {
    this.#storage = storage;
  }

  ...

  set theme(darkMode) {
    this.#storage.theme = darkMode;
  }

  get theme() {
    return this.#storage.theme;
  }
}

const storage = localStorage;
export default new Store(storage);

localStorage์—์„œ theme๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋‹ค

๊ฒฐ๊ณผ

์†Œ๊ฐ

์‚ฌ์‹ค ์–ด๋ ต์ง€๋Š” ์•Š์•˜๋‹ค. ํ•˜์ง€๋งŒ ํ† ๋ผ ์•„์ด์ฝ˜์„ ์ฐพ๋Š”๊ฒŒ ์ •๋ง ๋„ˆ๋ฌด๋„ˆ๋ฌด๋„ˆ๋ฌด ํž˜๋“ค์—ˆ๋‹ค...๐Ÿ˜ญ ํฌ๊ธฐํ•˜๊ณ  ๊ณ ์–‘์ด ๋กœ๊ณ ๋งŒ ๋ณด์—ฌ์ค„๊นŒ๋„ ์ƒ๊ฐํ–ˆ์ง€๋งŒ ์ปจ์…‰์— ์ตœ๋Œ€ํ•œ ๋งž๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์–ด์„œ ์—ด์‹ฌํžˆ ์ฐพ์•˜๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ† ๋ผ ์•„์ด์ฝ˜์ด

<span class="material-icons">cruelty_free</span>

์ด๋Ÿฐ์‹์œผ๋กœ ๋˜์–ด ์žˆ์–ด์„œ ์ธํ„ฐ๋„ท์ด ๋А๋ฆฌ๋ฉด ํ† ๋ผ ์•„์ด์ฝ˜ ๋Œ€์‹  cruelty_free๋ผ๋Š” ๊ธ€์”จ๊ฐ€ ๋ณด์ผ๋•Œ๊ฐ€ ์žˆ์–ด์„œ ์‹ ๊ฒฝ์ด ์•ฝ๊ฐ„ ์“ฐ์ธ๋‹ค.

profile
Software developer

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