๐ŸŽ„ ์„ฑ์žฅ์ผ์ง€ 4.0

์ฑ… ํ–‰๋ณตํ•œ ์ด๊ธฐ์ฃผ์˜์ž(์›จ์ธ ๋‹ค์ด์–ด)์˜ ๋‚ด์šฉ์— ์ž๊ทน๋ฐ›์•„ ์‹œ์ž‘ํ•˜๋Š” ์†Œ๋ฐ•ํ•œ ์„ฑ์žฅ๊ธฐ๋ก

์‚ด์•„์žˆ๋Š” ๊ฝƒ๊ณผ ์ฃฝ์€ ๊ฝƒ์€ ์–ด๋–ป๊ฒŒ ๊ตฌ๋ณ„ํ•˜๋Š”๊ฐ€?
์„ฑ์žฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์‚ด์•„ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
์ƒ๋ช…์˜ ์œ ์ผํ•œ ์ฆ๊ฑฐ๋Š” ์„ฑ์žฅ์ด๋‹ค!

๐ŸŒณ (1.0)ํ‚ค์›Œ๋“œ
์ตœ๋Œ€ํ•œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌ, ์ถ”ํ›„์— ๋ณด๋ฉด์„œ ์Šค์Šค๋กœ ์„ค๋ช…
๐Ÿ‰ (2.0)๊ฒฝํ—˜ ์œ„์ฃผ๋กœ
๋‹จ์ˆœ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ๋ณด๋‹ค ๋ฌด์—‡์„ ๋ฐฐ์› ๊ณ  ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋Š”์ง€ ์งง๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์ž‘์„ฑ
โ„๏ธ (3.0)์ •ํ•ด์ง„ ํ…œํ”Œ๋ฆฟ์— ๋งž์ถฐ์„œ
ํ‚ค์›Œ๋“œ, ๊ฒฝํ—˜ ๋ชจ๋‘ ์ข‹๋‹ค. ๋‹ค๋งŒ ๋งค์ผ ์ž‘์„ฑํ•˜๊ธฐ๋กœ ๋งˆ์Œ ๋จน์€๋งŒํผ ํ•ต์‹ฌ๋งŒ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ…œํ”Œ๋ฆฟ์„ ์ž‘์„ฑ
(3.1)230102๋ถ€ํ„ฐ ์‹œ์ž‘๋˜๋Š” ํ•™์Šต์— ๊ด€ํ•œ ๋‚ด์šฉ ์ถ”๊ฐ€
(3.2)230313๋ถ€ํ„ฐ ์ข€๋” ๊ฒฝํ—˜, ๊ฐ์ • ์œ„์ฃผ์˜ ๋‚ด์šฉ๋„ ๋‹ด๊ธฐ!
๐ŸŒพ (4.0)ํ•™์Šต ํ‚ค์›Œ๋“œ์—์„œ ์ตœ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์ •๋ณด ์ œ๊ณต, ๊ณ ๋ฏผ์—์„œ ๋‚ด ๊ฒฝํ—˜์„ ์ž์„ธํžˆ ์ ์ž!

๐Ÿง ๊ณ ๋ฏผ ์‚ฌํ•ญ

๐Ÿ”‘ ์˜ค๋Š˜์˜ ํ•™์Šต ํ‚ค์›Œ๋“œ

  • ๋ฆฌํŒฉํ† ๋ง

๐Ÿฅณ ํ•™์Šต ๋‚ด์šฉ

๋ฆฌํŒฉํ† ๋ง ์„ฑ๊ณต?!

๋“œ๋””์–ดโ€ฆ ๋ช‡์ผ๋™์•ˆ ํ˜ผ์ž ๋™๋™๊ฑฐ๋ฆฌ๋˜ ๋ฆฌํŒฉํ† ๋ง์„ ์„ฑ๊ณตํ–ˆ๋‹ค!!! ์•„์ฃผ ๊ฐ„๋‹จํ•œ Header๋งŒ ๊ธฐ์กด๊ณผ ๋น„๊ตํ•ด๋ณด์ž๋ฉด Header๋Š” Model, Component, View ์ด 3๊ฐ€์ง€๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค.

Model์€ ํฐ ๋ณ€ํ™”๊ฐ€ ์—†๊ธฐ์— View์™€ Component๋งŒ ์‚ดํŽด๋ณด๋ ค ํ•œ๋‹ค.

๋ฆฌํŒฉํ† ๋ง ์ „ ์ฝ”๋“œ

View

import { State } from '@custom-types/types';
import { AbstractView } from '@custom-types/abstracts.js';

export class HeaderView extends AbstractView {
  constructor() {
    super();
  }

  protected setWrapper() {
    this._wrapperElement.innerHTML = `<header class="h-1/6 bg-green-100 border border-green-500 text-3xl text-gray-500 grid place-content-center">Header</header>`;
  }

  render(state: State) {
    return;
  }
}

Component

import { Props, State } from '@custom-types/types';
import { Component } from '@custom-types/interfaces';
import { HeaderModel } from '@components/header/HeaderModel.js';
import { HeaderView } from '@components/header/HeaderView.js';

export class HeaderComponent implements Component {
  private _model: HeaderModel;
  private _view: HeaderView;
  constructor(props?: Props) {
    this._model = new HeaderModel();
    this._view = new HeaderView();
  }

  get element() {
    return this._view.element;
  }

  get state() {
    return this._model.state;
  }
  private setState(state: State) {
    this._model.setState(state);
    this._view.render(this._model.state);
  }

  attachTo(component: Component, position: InsertPosition = 'beforeend') {
    component.element.insertAdjacentElement(position, this.element);
  }
}

์œ„์—์„œ ๋ฌธ์ œ๋Š” View์˜ render ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹จ์ˆœํžˆ ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ ํ…œํ”Œ๋ฆฟ์— ์ด๋ฒคํŠธ๋ฅผ ๋ถ™์ด๊ฑฐ๋‚˜ dom api๋ฅผ ํ†ตํ•œ ์ง์ ‘ ์ ‘๊ทผ์œผ๋กœ ๋ฐ”๋€ state๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ui์˜ ๋ชจ์Šต์ด ๋ณ€ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค. ๋˜, Component์—์„œ ๊ตฌํ˜„๋œ attachTo๋ผ๋Š” ๋ฉ”์„œ๋“œ๋Š” ๊ฐ€๋…์„ฑ์€ ์ข‹์ง€๋งŒ ๋ถ€๋ชจ์— ํ•œ๋ฒˆ element ํ˜•ํƒœ๋กœ ๋ถ™์—ฌ๋ฒ„๋ฆฌ๋ฉด ์ดํ›„์— ์ด element์— innerHTML ํ˜•ํƒœ๋กœ ์ƒˆ๋กœ์šด state๋ฅผ ๋„˜๊ธด template literal์„ ์ „๋‹ฌํ•  ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.(๋ถ€๋ชจ์— ๋ถ™์ธ ์ˆœ๊ฐ„ ์ด๋ฏธ ์ •์ ์ธ element๊ฐ€ ๋ถ™์–ด๋ฒ„๋ฆฌ๊ณ  ๊ทธ ๋’ค์— ๊ฐ’์€ ๋ฐ”๊พผ๋‹ค๊ณ  ๋ถ€๋ชจ์— ๋ถ™์€ element๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.) ๊ทธ๋ฆฌ๊ณ  ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๋Š” ์ฒ˜์Œ ๋‚ด๊ฐ€ ์„ค๊ณ„ํ–ˆ๋˜ ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค Model, View๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” ๋ฆฌ์•กํŠธ์Šค๋Ÿฌ์šด ๋Š๋‚Œ์ด ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ด๋‹ค. setState๊ฐ€ ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค render ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ ์ƒˆ๋กœ์šด state๊ฐ€ ๋ฐ˜์˜๋œ ui๋กœ ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ ์œ„ ์ฝ”๋“œ๋Š” ์•ž์„œ ๋งํ•œ ๋ฌธ์ œ๋“ค๋กœ ๋˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋Ÿผ ์ด์ œ ์ƒˆ๋กœ์šด ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด์ž..!

๋ฆฌํŒฉํ† ๋ง ํ›„ ์ฝ”๋“œ

View

import { State } from '@custom-types/types';
import { TempAbstractView } from '@custom-types/abstracts.js';

export class HeaderView extends TempAbstractView {
  constructor($target: HTMLElement) {
    super($target);
  }

  template(state: State) {
    return `<header class="h-full bg-green-100 border border-green-500 text-3xl text-gray-500 grid place-content-center">Header</header>`;
  }

  render(state: State) {
    this.$target.innerHTML = this.template(state);
    this.addChildren(state);
    this.setEvents(state);
  }

  setEvents(state: State) {
    return;
  }

  addChildren(state: State) {
    return;
  }
}

Component

import { Props, State } from '@custom-types/types';
import { TempComponent } from '@custom-types/interfaces';
import { HeaderModel } from '@components/header/HeaderModel.js';
import { HeaderView } from '@components/header/HeaderView.js';

export class HeaderComponent implements TempComponent {
  private _model: HeaderModel;
  private _view: HeaderView;
  $target: HTMLElement;

  constructor(targetElement?: HTMLElement, props?: Props) {
    this.$target = targetElement as HTMLElement;
    this._model = new HeaderModel();
    this._view = new HeaderView(this.$target);
  }

  get state() {
    return this._model.state;
  }

  private setState(state: State) {
    this._model.setState(state);
    this._view.render(this._model.state);
  }
}

์œ„์˜ Header ์ปดํฌ๋„ŒํŠธ๋Š” ๋”ฐ๋กœ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—†์–ด์„œ props๋ฅผ ์ „๋‹ฌํ•˜๊ฑฐ๋‚˜ ์ž์‹์„ ๋ถ™์ผ ์ผ์ด ์—†๋‹ค. ์œ„์—์„œ ํ•ต์‹ฌ์€ ๋ถ€๋ชจ(target)์„ ์ „๋‹ฌํ•˜์—ฌ ์ž์‹์—์„œ ๋ถ€๋ชจ์˜ element์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ์œผ๋กœ์จ innerHTML๋กœ ์ƒˆ๋กœ์šด state์— ๋”ฐ๋ฅธ ui๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ ์ ์ด๋‹ค.

์œ„์˜ ๋‚ด์šฉ๋“ค์€ ์ถ”ํ›„์— ๋” ์ž์„ธํžˆ ๊ธธ๊ฒŒ ๋‹ค๋ค„๋ณผ ์˜ˆ์ •์ด๋‹ค.(๋ฆฌํŒฉํ† ๋ง ์ •๋ง ํž˜๋“ค์—ˆ๋‹คโ€ฆใ…  ๋ฌผ๋ก  ์ด์ œ์„œ์•ผ ์›ƒ์œผ๋ฉด์„œ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์ง€๋งŒ..!)