React에서 클래스형 컴포넌트를 학습하며 bind() 학습에 많은 시간이 들었습니다.
bind()를 학습하며 해소한 어려움을 잊지 않기 위해 기록합니다.
STEP 1. 클래스형 컴포넌트의 bind()
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true }; // 초기 상태 설정
// 이벤트 핸들러에서 `this`를 바인딩
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// prevState를 사용해 상태를 업데이트
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "켜짐" : "꺼짐"}
</button>
);
}
}
학습한 예제 코드는 Toggle
클래스형 컴포넌트입니다.
학습 시간이 오래걸린 코드는 this.handleClick = this.handleClick.bind(this);
부분입니다.
<button onClick={this.handleClick}>
정의된 handleClick()
메서드는 이벤트 핸들러 메서드
입니다.
이벤트 핸들러 메서드는 React에서 DOM 이벤트로 전달할 때 사용합니다.
"예제 코드에서 this.handleClick = this.handleClick.bind(this);
가 주석처리 하고 빌드한다는 가정"
this.handleClick = this.handleClick.bind(this);
가 주석처리 되어진 상태로 빌드하면 Toggle
클래스형 컴포넌트의 인스턴스가 생성되어 React에서 관리가 될 것이고, Toggle
클래스형 컴포넌트를 사용하는 페이지에선 렌더링 작업을 수행할 때 해당 인스턴스의 render() 메서드를 호출하여 UI를 생성합니다.
랜더링이 완료된 페이지에 처음 접근하면 isToggleOn
상태가 true
이므로 "켜짐" 텍스트를 확인할 수 있습니다.
이후, 버튼을 클릭했을 때 의도했던 것처럼 "꺼짐"이 제대로 동작할까요?
결론적으로는 아닙니다. 브라우저는 오류 페이지를 출력하고 개발자 도구 콘솔에는 TypeError: Cannot read properties of undefined (reading 'setState')
라는 React 애플리케이션 런타임 오류가 발생하게 됩니다. 결론적으로 이러한 문제가 생기는 이유는 this 바인딩
에 있습니다.
this 바인딩
이 제대로 되지 않아 this.setState()
를 찾으려하지만 찾을 수 없다. 즉, 정의되지 않았다 라는 undefiend가 출력되는 것입니다. 이를 해결하기 위해서는 React의 기본 동작에 대해서 이해해야합니다.
<button onClick={this.handleClick}>
React는 이벤트 핸들러를 전달할 때 메서드를 넘깁니다. 메서드도 함수이므로 메서드를 전달한다는 것은 함수 객체를 전달한다는 뜻입니다. 즉, 본질적으로 메서드와 함수는 동일한 Javascript 함수 객체입니다.
// bind()되지 않았을 때의 메모리 구조
toggleInstance
├── state: { isToggleOn: true }
├── props: {}
├── handleClick: function (원본 함수)
└── __proto__: Toggle.prototype
함수에서 this
를 사용하면 전역 객체를 참조합니다. 즉, undefined
나 window/global
를 가리키게 되는데 React는 기본적으로 엄격모드이기 때문에 함수 호출시 undefined
의 결과를 얻습니다.
이를 해결해주기 위해서는 this.handleClick = this.handleClick.bind(this);
코드를 constructor
내부에 작성해 이벤트 핸들러 메서드의 this
를 클래스 인스턴스로 고정하면 됩니다.
풀어 설명하자면 this.handleClick.bind(this)
를 호출시, 기존 메서드 handleClick
을 기반으로 새로운 함수 객체가 생성됩니다. 새롭게 만들어진 함수 객체는 this
가 명시적으로 클래스 인스턴스를 참조하도록 고정됩니다. 이때 기억해야 하는 것은 두 가지 입니다.
handleClick
함수 객체의 원본이 존재하며 이는 프로토타입 객체로 메모리에 상주한다.// Toggle.prototype Toggle.prototype ├── constructor: Toggle // 클래스 생성자 참조 ├── handleClick: function // 원본 handleClick 메서드 ├── render: function // render 메서드 └── __proto__: React.Component.prototype // React.Component 상속 // this 바인딩 인스턴스 toggleInstance ├── state: { isToggleOn: true } // 초기 state ├── handleClick: bound function // 바운드 함수 ├── props: {} // 전달된 props (초기에는 비어 있음) └── __proto__: Toggle.prototype // 프로토타입 체인 // handleClick 원본 handleClick (Function 객체) ├── [[FunctionLocation]]: 메모리 주소 ├── [[Scope]]: 렉시컬 환경 └── [[Prototype]]: Function.prototype // bind()로 새롭게 만들어진 객체 (bind로 생성된 함수) bound handleClick (Function 객체) ├── [[BoundTargetFunction]]: handleClick // 원본 함수 참조 ├── [[BoundThis]]: toggleInstance // this가 toggleInstance로 고정 ├── [[BoundArguments]]: [] // 미리 설정된 인수 없음 └── __proto__: Function.prototype
this
바인딩 된 함수 객체는 원본 객체를 참조하여 새로운 객체를 만든다. (클래스 인스턴스 고정)
⭐ "아 난 위의 내용이 이해가 되지 않는다!" 그러면 아래 코드로..
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
}
// 클래스 필드 문법으로 메서드를 정의
handleClick = () => {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn,
}));
};
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? '켜짐' : '꺼짐'}
</button>
);
}
}
만약 위의 내용들이 잘 이해가 되지 않는다면 handleClick
을 화살표 함수로 사용하면 this
바인딩이 자동적으로 되기 때문에 위와 같은 코드를 사용하면 됩니다.
'Framework && Library > React' 카테고리의 다른 글
[React] Context API 사용이유 및 용도, 유의사항, 예시코드 (0) | 2025.01.07 |
---|---|
[React] List 및 key의 개념, map()함수, key의 사용목적은 렌더링 효율에 있다. (0) | 2025.01.05 |
[React] useEffect() : Side effect 분리 Hook (0) | 2025.01.03 |