
์ด๋ฒ์ฃผ์ ํ๋ก๊ทธ๋๋จธ์ค ๋ฐ๋ธ์ฝ์ค์์ Vanilla Javascript๋ฅผ ์ด์ฉํด TodoList๋ฅผ ๋ง๋๋ ๊ณผ์ ๋ฅผ ์งํํ๋ค.
๋๋ ๋คํฌ๋ชจ๋ ๋ง๋ค๊ธฐ๋ฅผ ์ถ๊ฐ ๋ชฉํ๋ก ์ธ์ ๋ค.
์ด๋ค ์ปจ์
์ผ๋ก ๊ตฌํํ ์ง ์์ค๋ฅผ ์ฐพ์ ๋ค๋๋ค๊ฐ

์ฟ ๋ก๋ฏธ์ ๋ง์ด๋ฉ๋ก๋๋ฅผ ์ด์ฉํด ๋คํฌ๋ชจ๋๋ฅผ ๊ตฌํํ๊ธฐ๋ก ํ๋ค
div#app ํ๊ทธ์ 'dark' ํด๋์ค ์กด์ฌ ์ฌ๋ถ์ ๋ฐ๋ผ css๋ณ์ ๊ฐ์ด ๊ฒฐ์ ๋๋ฉฐ ๋คํฌ ๋ชจ๋๊ฐ ๋ฐ์๋๋ค
App์ด ์คํ๋๋ฉด localStorage์ ์ฌ์ฉ์ ์ธํ ์ ํ์ธํ๋ค
- localStorage์ theme๊ฐ ์ ์ฅ๋์ด ์์ผ๋ฉด theme์ ๋ฐ๋ผ ๋ชจ๋ ์ด๊ธฐํ
- localStorage์ theme๊ฐ ์ ์ฅ๋์ด ์์ง ์๋ค๋ฉด ์ฌ์ฉ์ ์ธํ ์ ๋ง์ถฐ์ ๋ชจ๋ ์ด๊ธฐํ
- ๋คํฌ๋ชจ๋๋ผ๋ฉด div์ dark ํด๋์ค ์ถ๊ฐ
- ๊ทธ๋ ์ง ์๋ค๋ฉด div์์ dark ํด๋์ค ์ ๊ฑฐ
: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>`;
...
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';
}
}
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>`;
}
}
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๋ผ๋ ๊ธ์จ๊ฐ ๋ณด์ผ๋๊ฐ ์์ด์ ์ ๊ฒฝ์ด ์ฝ๊ฐ ์ฐ์ธ๋ค.