[황준일]Vanilla Javascript로 웹 컴포넌트 만들기를 읽은 후기

jaemin·2023년 4월 13일

render VS mounted

컴포넌트를 분할하면서, 원래 render 메서드 하나로만 관리하다가 render 메서드mounted 메서드를 분리했다. render와 mounted를 분리한 이유가 뭘까?
render 안에서 mounted가 호출된다. 만약 특정 돔에 하위 컴포넌트를 추가해야 할 때 특정 돔이 만들어지지 않았을 수 있기 때문에

<div id="app"></div>

처럼 미리 존재해야할 돔을 미리 렌더링 되어야 한다. 이 과정을 render에서 하고 나중에 추가돼야할 컴포넌트는 mounted에서 렌더링 된다.

this

this binding이 필요한 이유

  • src/App.js
class App extends Component {
	mounted() {
      new Items($items, {
        /*
        filteredItems,
        toggleItem: toggleItem.bind(this),
        */![](https://velog.velcdn.com/images/kim-jaemin420/post/b680d4f5-6fd4-4d50-a70e-edc8d982c752/image.png)

        deleteItem: deleteItem.bind(this),
        
      })
	}
  
    deleteItem(seq) {
      const items = [...this.state.items];
      
      items.splice(items.findIndex(item => item.seq === seq), 1);
      this.setState({ items });
    }
}

export default App;

여기서 deleteItem, toggleItem 메서드에 this를 바인딩해주는 이유가 뭘까?

  • src/components/Items.js
class Items {
	setEvent() {
      const { deleteItem, toggleItem } = this.props;

      this.addEvent('click', '.deleteBtn', ({target}) => {
        deleteItem(Number(target.closest('[data-seq]').dataset.seq));
      });
    }
}

export default Items;

Items 컴포넌트에서 this.props.deleteItem으로 접근 가능하다. 그리고 deleteItems 안에서는 this.state.items를 참조하고 있다.
this는 함수를 호출하는 형태에 따라 바인딩이 달라진다.

호출 방식에 따른 this

  • 일반 함수처럼 호출: window
  • 메소드로 호출: 메서드를 호출한 객체
  • 생성자 함수 호출 : 생성자 함수가 (미래에) 생성할 인스턴스

처음 글을 읽을때, this binding을 왜 꼭 해줘야하는지 이해가 안돼서 binding 해주지 않고 this.props로 넘겨줘봤다.

이때, Items에서 호출하게 되면, deleteItem를 일반 함수 호출하듯이 호출했으므로 deleteItem 메서드 안에서 참조하는 this는 window 객체를 가리키고 있을거라고 예상했다.

  • src/App.js
class App {
	mounted() {
      new Items($items, {
        deleteItem,
        //...
      })
	}
  
    deleteItem() {
      // this???
    }
}

export defualt App
class Items {
	setEvent() {
      const { deleteItem, toggleItem } = this.props;

      this.addEvent('click', '.deleteBtn', ({target}) => {
        deleteItem(Number(target.closest('[data-seq]').dataset.seq));
      });
    }
}

그러나 실제로 window 객체가 찍히는게 아니라 undefined가 찍히는 것을 볼 수 있었다.

strict mode일 경우 일반 함수로 호출 시 this는 undefined가 바인딩 되는데, 따로 strict mode를 설정하지 않았는데 undefined가 되는게 이해가 가지 않았다.
알고 보니 module을 사용하면 자동으로 strict mode가 되기 때문에 this가 undefined로 잡히는 것을 알 수 있었다.

그렇다면, Items에서 일반 함수가 아닌 this.props.deleteItem()으로 호출했을때 deleteItem 메서드 안에서의 this는 어떤 것을 가리키게 될까?
이 경우에 this는 당연히 this.props를 가리키게 된다.

deleteItem 메서드안에서 필요한 this는 this.props객체가 아니라 App 생성자 함수가 만들어낸 인스턴스 객체이다. 이렇게 메서드를 어떻게 호출되냐에 따라 this가 변경되어 혼동을 준다. 또 다른 컴포넌트에서 deleteItem 메서드를 호출시켜 App의 상태를 변경시키므로 this binding 없이는 App의 상태를 변경할 방법이 없다. 따라서 꼭 binding을 해줘야 한다는 것을 알 수 있었다.

this binding을 하고 메서드를 내 맘대로 호출하면?

this binding을 해주어도 함수를 내 마음대로 호출하면 어떻게 될지 궁금해졌다.

  • src/App.js
class App {
	mounted() {
      new Items($items, {
        deleteItem: deleteItem.bind(this),
        //...
      })
	}
  
    deleteItem() {...}
}

export defualt App
  • src/components/Items.js
class Items {
	setEvent() {
      this.addEvent('click', '.deleteBtn', ({target}) => {
        this.props.deleteItem(Number(target.closest('[data-seq]').dataset.seq));
      });
    }
}

this.props.deleteItem()으로 호출했음에도 불구하고 bind 해준 this가 찍히는 걸 알 수 있다.

profile
프론트엔드 개발자가 되기 위해 공부 중입니다.

0개의 댓글