[TIL] 0307

yoon Y·2022년 3월 8일
0

2022 - TIL

목록 보기
52/109

TypeScript Project

최종적으로 컴포넌트 템플릿 구조를 완성했다

문제점

파라미터로 받아온 부모 돔에 innerHTML로 템플릿을 생성하고, 부모 돔에 이벤트를 걸어주어 위임하는 방식으로 구현을 했었지만 여러 문제점이 있었다

  • 한 컴포넌트에 여러 자식을 연결해주려면 해당 부모 컴포넌트 템플릿에서 자식을 연결해줄 태그를 매번 추가해야 했고
  • 부모 컴포넌트 렌더링(템플릿 생성) 후에 자식 컴포넌트를 생성해 연결해주는 방식이기 떄문에
    부모의 상태가 바뀌면 상태변화에 영향을 받지 않는 컴포넌트까지 불필요하게 재생성되었다

해결책

  • 파라미터로 $target(부모 돔), props외에 추가로 태그 이름까지 받아온다
  • constructor함수에서 받아온 태그이름으로 해당 컴포넌트 자신의 돔을 만들어 $myDom프로퍼티에 저장한다
  • 자식 컴포넌트를 연결하고 싶다면 constructor함수 안에서 클래스를 실행시킨 후 인스턴스를 반환받는데, $myDom을 $target으로 넣어준다
  • 부모의 상태변화에 따라 재렌더링을 해야하는 자식 컴포넌트가 있다면 부모의 setState함수 안에서
    instanse.setState(부모의 상태)를 실행시켜준다

모든 자식 컴포넌트는 부모 컴포넌트 첫 생성 시에 같이 생성되어 연결되고,
부모의 상태 변화에 영향을 받는 컴포넌트만 필요에 따라 재렌더링 시켜줄 수 있다

// 적용
class RandomQuotes extends Component<undefined, { [key: string]: string }> {
  $contents;
  $changeButton;
  constructor($target: Element, tagName: string) {
    super($target, tagName);
    this.$contents = new Contents(this.$myDom, 'div', { ...this.state });
    this.$changeButton = new ChangeButton(this.$myDom, 'button', {
      onClick: this.handleClickButton.bind(this),
    });
  }

  setup() {
    this.state = {
      quote: '',
      author: 'RandomQuotes',
    };
    this.setSelector(this.$myDom, 'quotes-container');
  }

  setState(newState: { [key: string]: string }) {
    this.state = { ...this.state, ...newState };
    this.$contents.setState(newState);
  }

  async handleClickButton() {
    const { data } = await axios.get('https://free-quotes-api.herokuapp.com/');
    this.setState({ quote: data.quote, author: data.author });
  }
}

export default RandomQuotes;

// 템플릿
export default class Component<P, S> {
  $target: Element;
  $myDom: Element;
  props: P;
  state?: S;
  constructor($target: Element, tagName?: string, props?: P) {
    this.$target = $target;
    this.$myDom = document.createElement(tagName);
    this.$target.appendChild(this.$myDom);
    this.props = props;
    this.setup();
    this.setEvent();
    this.render();
  }

  setup() {
    return;
  }

  setSelector($dom: Element, name: string) {
    if ($dom instanceof HTMLElement) {
      $dom.dataset.name = name;
    }
  }

  mounted() {
    return;
  }

  template() {
    return '';
  }

  render() {
    this.$myDom.innerHTML = this.template();
    this.mounted();
  }

  setEvent() {
    return;
  }

  setState(newState: S) {
    this.state = { ...this.state, ...newState };
    this.render();
  }

  addEvent(eventType: string, callback: (e: Event) => void) {
    this.$myDom.addEventListener(eventType, callback);
  }

  addEventToParent(
    eventType: string,
    selector: string,
    callback: (e: Event) => void,
  ) {
    const children = [...Array.from(this.$target.querySelectorAll(selector))];

    const isTarget = (target: EventTarget): boolean | Element => {
      if (target instanceof HTMLElement) {
        return children.includes(target) || target.closest(selector);
      }
    };

    this.$target.addEventListener(eventType, event => {
      if (!isTarget(event.target)) return false;
      callback(event);
    });
  }
}
profile
#프론트엔드

0개의 댓글