testdome에서 재미난 리액트 문제를 발견했다. 지금은 비록 사용하지 않는 클래스 컴포넌트이지만, this키워드를 이해하기 좋은 문제라 생각하여 해설해보려고 한다.
문제는 일단 다음과 같다. ( 출처: testdome )
// React is loaded and is available as React and ReactDOM
// imports should NOT be used
const sampleOptions = [
{ id: "753", title: "This is the first option" },
{ id: "035", title: "This is the second option" }
];
class OptionsShower extends React.Component {
constructor(props) {
super(props);
const { options } = props;
this.state = { options, displayOptions: false };
}
displayOptions() {
this.setState({
options: this.state.options,
displayOptions: !this.state.displayOptions
});
}
render() {
var options = null;
if (this.state.displayOptions) {
options = (
<ul id="options">
{this.state.options.map(option => (
<li key={option.id}>{option.title}</li>
))}
</ul>
);
}
return (
<div>
<button onClick={this.displayOptions}>
{this.state.displayOptions ? "Hide options" : "Show options"}
</button>
{options}
</div>
);
}
}
document.body.innerHTML = "<div id='root'></div>";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<OptionsShower options={sampleOptions} />);
위와 같은 코드로 실행이 되면, type error가 발생한다.
왜 인지 살펴보고, 이 문제를 해결하기 위해서 어떻게 코드를 바꿀지 생각해보자.
class형 컴포넌트에서는 props, state, 생명주기 메소드를 가르키는 데 this키워드를 자주 사용한다.
그렇기에, this가 무엇을 가르키는 지 계속해서 신경을 써야한다.
import React from "react";
class App extends React.Component {
componentDidMount() {
console.log("componentDidMount의 this는", this); //App
}
render() {
console.log("render()의 this는", this); //App
return (
<div>
<h1 >Class Component this</h1>
</div>
);
}
}
export default App;
위와 같은 코드에서는 this가 메소드를 호출한 해당 컴포넌트를 가리키고 있기 때문에 App 컴포넌트가 콘솔창에 잘 찍히고 있다.
이렇듯 컴포넌트에서 선언한 메소드들은 기본적으로 해당 컴포넌트를 바인딩하고 있다.
하지만, 다음과 같이 this를 지정하면 어떻게 될까?
import React from "react";
class App extends React.Component {
state = {
num: 0
};
componentDidMount() {
console.log("componentDidMount의 this는", this); //App
}
click = () => {
console.log("click함수 this", this);
};
render() {
console.log("render()의 this는", this); //App
return (
<div>
<h1 onClick={this.click}>Class Component this</h1>
</div>
);
}
}
export default App;
위와 같이 코드를 작성해, h1태그를 클릭하여 click함수를 작동시키면, 아래와 같은 TypeError가 발생한다.
왜 일까?
TypeError이므로, 우리는 click함수 내에서 this가 해당 컴포넌트를 가르키고 있지 않아 발생하는 오류임을 짐작할 수 있다.
this키워드가 일반 함수에서는 함수가 호출되는 방식에 따라 달라지므로, 콘솔창에 찍어보면 undefined가 찍힘을 알 수 있다.
화살표함수는 this가 상위 스코프 (렉시컬 스코프)를 가리킨다.
우리는 일전에 배운 this키워드에서 화살표함수에서의 this가 상위 스코프의 객체를 가리키며, bind와 같은 메소드를 사용할 수 없음을 배웠습니다.
따라서, 기존의 코드에서 click함수를 일반 함수가 아닌, 화살표함수로 바꾼다면 this가 상위 스코프인 App컴포넌트를 가리키므로 정상적으로 작동함을 알 수 있습니다.
아래 코드는 click함수를 일반 함수에서 화살표함수로만 바꾸어줬습니다.
import React from "react";
class App extends React.Component {
state = {
num: 0
};
componentDidMount() {
console.log("componentDidMount의 this는", this); //App
}
click = () => {
console.log("click함수 this", this);
};
render() {
console.log("render()의 this는", this); //App
return (
<div>
<h1 onClick={this.click}>Class Component this</h1>
</div>
);
}
}
export default App;
아래와 같이 App컴포넌트가 콘솔창에 잘 찍히고 있는 것을 알 수 있다.
그러니까 처음에 말한 문제의 정답은 displayOptions의 함수를 화살표함수로만 바꿔주면 된다.
다시 한 번 복기해보는 화살표함수에서의 this키워드 특징 ( this가 없다고 말해야 하나? )
- 화살표 함수는 익명 함수로만 만들 수 있습니다.
- 화살표 함수는 생성자로 사용할 수 없습니다.
- 화살표 함수는 스스로의 this, argument 를 가지지 않습니다.
- 함수가 정의된 스코프에 존재하는 this 를 가리킵니다.
- 화살표 함수는 생성될 때 this 가 결정됩니다.
- 화살표 함수가 어떻게 사용되건, 호출되건, this 는 바뀌지 않습니다.
다시 한 번 복기해보는 일반함수에서의 this키워드 특징
- global scope 에서 사용될 때 this 는 전역 객체를 가리킵니다.(window 객체)
- 함수에서 사용될 때에도 this 는 전역 객체를 가리킵니다.
- 객체에 속한 메소드에서 사용될 때 this 는 메소드가 속한 객체를 가리킵니다.
- 객체에 속한 메소드의 내부함수에서 사용될 때 this 는 전역 객체를 가리킵니다.
- 생성자에서 사용될 때 this 는 이 생성자로 인해 생성된 새로운 객체를 가리킵니다.
'React' 카테고리의 다른 글
[React] 카카오톡 간편인증관련 (0) | 2023.02.24 |
---|---|
네이버클라우드에서 Nginx적용 후, 리액트 배포하기 (0) | 2023.02.14 |
useMemo와 useCallback을 이용한 렌더링 최적화 (0) | 2022.12.18 |
useEffect는 비동기적이지 않다. (0) | 2022.12.12 |
10주간 리액트를 쓰면서, 리액트를 몰랐다. (0) | 2022.12.11 |