viewmodelUpdated 부분 개선

const Binder = class extends ViewModelListener {
  #items = new Set();
  #processors = {};

  ...

  viewmodelUpdated(viewmodel, updated) { // 전역에서 끌고오던 부분을 인자로 수정
    const items = {};
    this.#items.forEach((item) => {
      items[item.viewmodel] = [
        type(viewmodel[item.viewmodel], ViewModel),
        item.el
      ];
    });
    updated.forEach(({ subKey, category, key, value }) => {
      if (!items[subKey]) return;
      const [vm, el] = items[subKey];
      const processor = this.#processors[category];
      if (!el || !processor) return;
      processor.process(vm, el, key, value);
    });
  }
};
const ViewModelSubject = class extends ViewModelListener {
  // ... 생략

	// 상속받은 애가 this.notifyTarget getter으로 뭘 보낼지 결정할 수 있게함
  notify () { this.#listeners.forEach(v => v.viewmodelUpdated(this.notifyTarget, this.#info)) }
  get notifyTarget () { throw 'must be override!' } // ViewModel에게 위임한다.
}

const ViewModel = class extends ViewModelSubject {
  // ... 생략
  get notifyTarget () { return this } // notifyTarget을 호출하면 ViewModel을 보낸다.
}
const Binder = class extends ViewModelListener {
  // .. 생략
  viewmodelUpdated(target, updated,
                   _ = type(target, ViewModel)){ // target은 ViewModel 이여야 한다.
    const items = {}
    this.#items.forEach(({ vmName, el }) => {
      items[vmName] = [type(target[vmName], ViewModel), el]
    })
    updated.forEach(({ subKey, category, k, v }) => {
      if (!items[subKey]) return
      const [vm, el] = items[subKey], processor = this.#processors[category]
      if (!el || !processor) return
      processor.process(vm, el, k, v)
    })
  }
}

getter

Object API, Proxy, Symbol 정리

Processor 개선

[문제점]

데이터 구조가 굳어 져서 처리기의 동적인 형태추가 로직 막은 구조

// Binder의 Render 부분
const Binder = class extends ViewModelListener {
  // ... 생략
  render (viewmodel, _ = type(viewmodel, ViewModel)) {
    const processores = Object.entries(this.#processors)
    this.#items.forEach(({ vmName, el }) => {
      const vm = type(viewmodel[vmName], ViewModel)
      processores.forEach(([pk, processor]) => {
        Object.entries(vm[pk]).forEach(([k, v]) => {
          processor.process(vm, el, k, v)
        })
      })
    })
  }
}

// ViewModel
const ViewModel = class extends ViewModelSubject {
  // ... 생략
  constructor(data, _ = type(data, 'object')) {
    super();
    Object.entries(data).forEach(([k, v]) => {
      if('styles,attributes,properties'.includes(k)) {
        if(!v || typeof v != 'object') throw `invalid object k: ${k}, v:${v}`
        this[k] = ViewModel.define(this, k, v)
      } else {
        Object.defineProperty(this, k, ViewModel.descriptor(this, '', k, v))
        if (v instanceof ViewModel) {
          v._setParent(this, k)
        }
      }
    })
    Object.seal(this)
  }
}

// Client 코드
binder.addProcessor(new class extends Processor {
  _process (vm, el, k, v) { el.style[k] = v }
}('styles'))
binder.addProcessor(new class extends Processor {
  _process (vm, el, k, v) { el.setAttribute(k, v) }
}('attributes'))
binder.addProcessor(new class extends Processor {
  _process (vm, el, k, v) { el[k] = v }
}('properties'))
binder.addProcessor(new class extends Processor {
  _process (vm, el, k, v) { el[`on${k}`] = e => v.call(el, e, vm) }
}('events'))

또, 데이터 형식이 자유롭지 않고 코드로 미리 다 정해버린 구조

const ViewModel = class extends ViewModelSubject {
  ...

  static descriptor = (vm, category, k, v) => ({
    enumerable: true,
    get: () => v,
    set: (newV) => {
      v = newV;
      vm.add(new ViewModelValue(vm.subKey, category, k, v));
    }
  });

  static defineProperties = (vm, category, obj) =>
    Object.defineProperties(
      obj,
      Object.entries(obj).reduce(
        (r, [k, v]) => ((r[k] = ViewModel.descriptor(vm, category, k, v)), r),
        {}
      )
    );

  constructor(data, _ = type(data, "object")) {
    super();
    Object.entries(data).forEach(([key, value]) => {
      if ("styles,attributes,properties".includes(key)) {
        this[key] = ViewModel.defineProperties(this, key, value);
      } else {
        Object.defineProperty(
          this,
          key,
          ViewModel.descriptor(this, "", key, value)
        );
        if (value instanceof ViewModel) {
          value.setParent(this, key);
        }
      }
    });
    Object.seal(this);
  }

....
};

...
const wrapper = ViewModel.get({
  styles: { width: "50%", background: "#ffa", cursor: "pointer" },
  events: {
    click(e, vm) {
      vm.parent.isStop = true;
    }
  }
});

자유롭게 기술하기 위한 방법