Why do we write super(props)? [번역]


Why do we write super(props)? 번역글입니다.


최근 "Hooks"에 대한 관심이 매우 많다고 들었습니다. 하지만, 저는 이번 블로그의 첫 포스팅으로 class component에 관해 이야기해보려 합니다. 그래도 괜찮겠죠?

여기서 소개할 내용에 대한 이해는 실제 React를 생산적으로 사용하는데 크게 중요하지는 않습니다. 하지만, 여러분이 어떻게 돌아가는지에 대에 아는 것을 즐긴다면, 재미있는 글이 될 것입니다.

첫 번째 내용은 아래와 같습니다.

class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}

class fields proposal을 이용하면 아래와 같이도 가능합니다.

class Checkbox extends React.Component {
  state = { isOn: true };
  // ...
}

2015년도 React 0.13에서 class를 지원할 때 위 문법에 대한 지원이 계획되었습니다. class field를 사용하기 전에는 Constructor를 정의하고 super(props)를 호출하는 것이 잠정적인 해결책이었습니다. 우리는 다시 ES2015 문법으로 돌아가 보겠습니다.

개인 주석 (원문 내용 아님)
검색을 해보니, class field는 아직 ECMAScript의 정식 표준은 아닙니다. 총 0 ~ 4단계 중 stage 3단계에 있습니다. Babel plugin을 통해서 사용할 수 있습니다. Chrome 브라우저에서는 72+ 에서 사용할 수 있습니다.

관련 Link: https://v8.dev/features/class-fields
class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}

우리는 왜 super를 호출할까요? 혹시 호출을 안 하면 안 될까요? 만약, 꼭 호출해야 한다면 props를 인자로 넘겨주지 않아도 괜찮을까요?

자바스크립트에서, super는 부모 객체를 참조합니다. (위 예에서는 React.Component를 가리킵니다.)

중요한 점은 constructor 안에서 부모 객체의 constructor를 호출하기 전까지 this 키워드를 사용할 수 없다는 것입니다.

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴 Can't use 'this' yet
    super(props);
    // ✅ Now it's okay though
    this.state = { isOn: true };
  }
  // ...
}

자바스크립트에서 this에 접근 전, 왜 부모 constructor를 먼저 실행해야 하는지에 대한 좋은 예가 있습니다.

class Person {
  constructor(name) {
    this.name = name;
  }
}

class PolitePerson extends Person {
  constructor(name) {
    this.greetColleagues(); // 🔴This is disallewed, read below why
    super(name);
  }
  greetCollegues() {
    alert('Good morning folks!');
  }
}

만약 super 호출 전 this를 사용할 수 있다고 가정해 봅시다. 위의 코드 작성 한 달 후, 아래와 같이 greetColleagues 호출 시, 메세지에 person의 이름을 추가하기 위해 아래와 같이 수정할 수도 있을 것입니다.

greetColleagues() {
  alert('Good morning folks!');
  alert('My name is ' + this.name + ', nice to meet you!');
}

하지만, 우리는 this.greetColleagues()super() 호출을 통해 this.name을 설정하기 전 호출된다는 점을 위와 같이 잊어버릴 수 있습니다. 이러한 코드 작성은 파악하기도 쉽지 않습니다.

위와 같은 문제를 피하려고 위해, constructor 안에서 this를 사용하고 싶다면 우리는 super를 먼저 호출해야 합니다. 그리고 이 점은 React의 class 기반 component에도 동일하게 적용되었습니다.

constructor(props) {
  super(props);
  // ✅ Okay to use 'this' now
  this.state = { isOn: true };
}

여기서 우리는 또 다른 궁금증이 생깁니다. props는 왜 전달할까요?

아마도 많은 분이 React.Componentthis.props를 초기화 할 수 있도록 propssuper 호출 시 전달한다고 생각할 것입니다.

// Inside React
class Component {
  constructor(props) {
    this.props = props;
  }
  // ...
}

그리고, 그것은 사실과 그리 다르지 않습니다. 실제, 그렇게 작동합니다. 하지만, 혹 우리가 superprops를 전달하는 것을 잊었다 해도, render나 다른 메소드에서 우리는 여전히 this.props에 접근할 수 있습니다.

어떻게 된 일일까요? 그 이유는 React가 constructor 호출 후 props를 instance에 할당해 주기 때문입니다.

// Inside React
const instance = new YourComponent(props);
instance.props = props;

우리가 super 호출 시 props 전달하는 것을 잊었다 해도, React가 그 후에 처리해 주는데, 이러한 이유가 있습니다.

React가 class를 지원하기 시작할 때, 꼭 ES6의 class만을 지원한 것은 아닙니다. React는 가능한 넓은 범위의 class를 지원하고자 했습니다. 당시에 컴포넌트를 정의하는 ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript, 또는 다른 여러 언어가 얼마나 성공적일지 확실하지 않았습니다. 그래서 ES6는 그렇지 않다고 해도, React는 내부적으로 super를 호출해야 할지 말지에 대해서는 중립적이었습니다.

하지만 이것이, 이것이 우리가 super(props) 대신 단순히 super()만 호출해도 된다는 뜻일까요?

그것은 혼란스러울 수 있기 때문에, 아마도 안 될 것입니다. 네, 앞서 설명한 대로 React는 constructor 호출 후 this.props을 할당합니다. 하지만, this.props는 constructor 호출 후 그리고 super 호출 전 단계에는 여전히 undefined입니다.

// 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
  }
  // ...
}

만약 위와 같은 상황이 constructor 안에서 호출된 어떠한 메소드에서 발생한다면, 디버킹 하기가 더 힘들어집니다. 그래서 props 전달이 꼭 필요한 상황이 아니더라도, super(props)와 같이 항상 props를 전달하는 것을 추천합니다.

class Button extends React.Component {
  constructor(props) {
    super(props); // ✅ We passed props
    console.log(props); // ✅ {}
    console.log(this.props); // ✅ {}
  }
}

React를 오래 사용해 본 User라면 또 다른 의문점이 들 수 있습니다.

class의 ContextAPI를 사용할 때(legacy contextTypes이던 또는 React 16.6의 contextType API이던) context를 constructor의 두 번째 argument로 전달합니다.

그러면 왜 우리는 super(props, context)로 쓰지 않을까요? 물론 쓸 수 있습니다. 하지만, context는 자주 사용하지 않기 때문에 위와 같은 문제의 여지가 크지 않습니다.

class fields proposal을 이용하면, 위와 같은 고민을 거의 하지 않아도 됩니다 constructor를 명시적으로 작성하지 않아도, 모든 arguments가 전달됩니다. 이는 this.props 또는 this.context를 참조하기 위해 state = {}와 같은 expression을 사용할 수 있는 이유이기도 합니다.

만약, Hooks를 사용한다면, super와 this를 사용할 필요가 없지만, 이것은 다른 주제로 준비하고자 합니다.