super를 호출해야하는 이유가 궁금해졌다.
super를 왜 호출해야할까? 호출하지 않으면 어떻게 될까? super를 호출하되 props 인자를 전달하지 않는다면 어떻게 될까?
자바스크립트에서 super는 부모클래스 생성자의 참조이다. 그리고 자바스크립트는 언어적 제약사항으로써 생성자에서 super를 호출하기 전에는 this를 사용할 수 없다.
예제 )
class Person {
constructor(name) {
this.name = name;
}
}
class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); // 🔴 This is disallowed, read below why
super(name);
}
greetColleagues() {
alert('Good morning folks!');
}
}
super 호출 전에도 this를 사용하는 것이 가능하다고 가정해보자. 아직까지는 문제가 없어보인다. 하지만 몇달 후에 greetColleagues 함수가 아래와 같이 this.name을 사용하도록 변경되었다고 해보자
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
this.name
이 초기화되기 전에 this.greetColleagues
가 호출되었다. 코드를 이해하기가 상당히 어려워졌다. 이렇게 애매한 경우를 허용하지 않기 위해 자바스크립트는 언어 차원에서 this
사용 전에 super
호출을 강제하는 것이다. 그리고 이 사항이 클래스 기반의 리액트 컴포넌트를 작성하는 데에도 동일하게 반영된 것이다.
constructor(props) {
super(props);
// ✅ Okay to use `this` now
this.state = { isOn: true };
}
super
를 반드시 호출해야 하는 이유는 설명이 되었지만 아직 해결되지 않은 질문이 하나 남았다. 왜 props
를 인자로 전달해야 할까?
아마도 React.Component
객체가 생성될 때 props
속성을 초기화하기 위해 부모 컴포넌트에게 props
를 전달하는 것이구나 라고 쉽게 예상할 수 있을 것이다.
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
그 예상은 틀리지 않았다. 그리고 실제로 그렇게 동작한다.
그러나 props
전달 없이 super()
를 호출하더라도 render
함수 및 기타 메소드에서 여전히 this.props
를 사용할 수 있습니다.
어떻게 그것이 가능할까? 리액트는 작성한 컴포넌트의 생성자 호출 이후 해당 객체에 props
속성을 세팅해준다.
// Inside React
const instance = new YourComponent(props);
instance.props = props;
그래서 리액트는 props
를 super
의 인자로 전달하는 것을 실수로 빠뜨리더라도 정상적으로 동작되는 것을 보장해준다.
리액트가 처음 클래스를 지원하기로 했을 때 단지 ES6
의 클래스만 지원하기로 했던 것은 아니었다. 최대한 범용적으로 여러가지 클래스 형태를 지원하고자 했다. 정확하지는 않지만 ClojureScript
, CoffeeScript
, ES6
, Fable
, Scala.js
, TypeScript
등에서 사용하기에도 문제가 없도록 하고자 했다. 그래서 리액트는 의도적으로 super
를 사용하는데 주저하지 않았다.
그렇다면 이것이 super()
를 사용하기 보다 super(props)
를 사용해야하는 충분한 이유가 될까?
아마도 그렇지 않을 것이다. 여전히 충분히 납득이 안되는 사람도 있을 것이다. 리액트는 작성한 생성자 호출 이후에 props
를 세팅해 준다. 생성자 내부에서 super()
가 호출되고 생성자가 끝나기 전까지 this.props
는 underfined
가 될 것이다.
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// Inside your code
class Button extends React.Component {
constructor(props) {
super(); // 😬 We forgot to pass props
console.log(props); // ✅ {}
console.log(this.props); // 😬 undefined
}
// ...
}
이렇게 this.props
가 undefined
인 생성자 내부에서 다른 함수를 또 호출하는 경우에 이로 인해 발생하는 문제들을 디버깅하는 일은 매우 흥미진진한 일이 된다.
이것이 바로 super(props)
를 꼭 호출해야만 하는 이유이다. 심지어 this.props
를 굳이 사용하지 않는 경우라도 말이다.
class Button extends React.Component {
constructor(props) {
super(props); // ✅ We passed props
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
이렇게 super(props)
를 호출하는 것은 생성자 내부에서도 this.props
를 정상적으로 사용할 수 있도록 보장해 준다.
아직 리액트 개발자들이 오랫동안 궁금해 했던 것이 아직 하나 남았다.
Context API를 사용할 경우 context가 생성자의 두번째 인자로 전달된다는 것을 알고 있었을 것이다. 그렇다면 왜 super(props,context)
와 같이 사용하지는 않을까? 그렇게 해도 된다. 그러나 context는 많이 사용되지 않기 때문에 이로 인한 문제는 별로 발생되지 않을 것이다.
어쨌든 class fields proposal 를 공식적으로 사용할 수 있을 때가 되면 super(props)
에 대한 복잡한 사항들은 더 이상 고민하지 않아도 될 것이다. 그때는 명시적인 생성자 선언 없이도 모든 인자를 편하게 사용할 수 있을 것이다.
This is what allows an expression like state = {} to include references to this.props or this.context if necessary.
Hooks 를 사용한다면 우린는 super
나 this
에 대해 고민하지 않아도 된다. 하지만 그것은 나중에 다루어야 할 주제이다.
[출처]https://min9nim.github.io/2018/12/super-props/
이 글은 Dan Abramov의 Why Do We Write super(props)? 글을 충분한 의역으로 번역한 것입니다. 번역이 일부 자연스럽지 않은 부분이 있을 수도 있습니다. 정확한 내용은 원문을 참고하기 바랍니다