컴포넌트 구조 최적화(3)

yoon Y·2022년 3월 20일
0

Vanilla Project

목록 보기
10/13

이전 방법의 문제점

constructure함수에서 컴포넌트 자신의 dom을 만들고, 자식 컴포넌트를 생성해 연결해주려면
인스턴스 생성 시에 해당 컴포넌트가 사용할 태그 이름을 props으로 넣어줘야하는데,
그럴 경우 같은 컴포넌트여도 각자 다른 태그로 생성될 수 있어, 컴포넌트 통일성에 있어서 좋지 않다고 판단했다.

클래스 확장 기능의 편리함을 어느정도 포기하고 컴포넌트를 선언할 때마다 constructor함수 안에 일일이 돔을 만드는 방법 생각해봤지만 기본 메소드들까지 매번 실행 시켜줘야하기 때문에 확장의 편리함을 가져갈 수가 없었다.

해결책

데브 매칭 과제를 대비하면서 작년의 출제된 "Vanilla JS ShoppingMall SPA"를 구현했는데,
이 과정에서 클래스 확장의 편리함과 부분 렌더링을 모두 가져가는 방법을 생각해냈다

  1. class 내부에 프로퍼티를 선언해준다
    • 꼭 선언해줘야 메소드에서 사용할 수 있다(나중에 할당하더라도)
    • constructor함수를 포함해서 모든 메소드 실행 전에 먼저 선언된다
export default class ProductDetailPage extends Component {
  $selectedOptions;
  $select;
  ...
  1. 템플릿 리터럴 렌더링 후 mounted함수에서 자식 컴포넌트들을 생성하는데 1번에서 만들어둔 프로퍼티에 할당한다
    • 이후에 다른 메소드에서도 사용할 수 있다
  createSelectedOptionsComponent() {
    const $selectedOptions = this.$target.querySelector(
      '.ProductDetail__selectedOptions',
    );
    const { productData, selectedOption } = this.state;
    const { productPrice } = productData;

    this.$selectedOptions = new SelectedOptions($selectedOptions, {
      productPrice,
      selectedOption,
      onChange: this.handleChangeInput.bind(this),
      onSubmit: this.handleSubmit.bind(this),
    });
  }

  mounted() {
    if (!this.state) {
      return;
    }
    this.createSelectComponent();
    this.createSelectedOptionsComponent();
  }
  1. 특정 자식 컴포넌트만 렌더링시키는 reRender메소드를 만들어서 프로퍼티에 저장된 자식 컴포넌트의 setState함수를 꺼내서 실행시킨다
  reRender() {
    const { selectedOption } = this.state;

    this.$selectedOptions.setState({
      selectedOption,
    });
  }
  • setState시에 2번째 인자에 따라 모든 돔을 재렌더링 시킬 지, 특정 돔만 렌더링 시킬 지 지정할 수 있게 수정했다
// template/Component.js

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

  reRender() {
   // 특정 자식 컴포넌트만 렌더링 시키는 코드(setState꺼내서 실행)
  }

  setState(newState, reRender = false) {
    this.state = { ...this.state, ...newState };
    reRender ? this.reRender() : this.render();
  }

동적으로 바뀌는 돔은 컴포넌트로, 바뀌지 않는 돔은 template literal로 작성하면된다!

템플릿 컴포넌트

export default class Component {
  $target;
  props;
  state;
  constructor($target, props) {
    this.$target = $target;
    this.props = props;
    this.setup();
    this.setEventToParent();
    this.render();
  }

  setup() {
    return;
  }

  mounted() {
    return;
  }

  template() {
    return '';
  }

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

  setEvent() {
    return;
  }

  setEventToParent() {
    return;
  }

  reRender() {}

  setState(newState, reRender = false) {
    this.state = { ...this.state, ...newState };
    reRender ? this.reRender() : this.render();
  }

  removeParentNode() {
    this.$target.replaceWith(...this.$target.childNodes);
  }
}

아쉬운 점

부분 렌더링의 제한된 조건
2depth이상에서 손자 컴포넌트 부분 렌더링이 가능하려면, 이벤트 발생 시 직속 부모의 상태에만 영향을 줘야한다. (1depth관계에서만 가능) 조상의 상태를 변경할 경우 직속 부모까지 전부 렌더링되기 때문이다.

자식 컴포넌트를 연결할 자리를(돔)을 만들어줘야한다
매 컴포넌트 마다 createElemet함수를 사용해 돔을 직접 만들고 appendChild를 해주는 방법이 너무 번거로워서 innerHTML+template literal을 이용하는 방법을 택했는데, 불필요한 부모 태그가 생성되어야하는 점은 피할 수 없었다.

profile
#프론트엔드

0개의 댓글