Framework && Library/React

[React] 클래스형 컴포넌트의 bind() + 대안

기록하는 습관. 2025. 1. 4. 03:46

 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이므로 "켜짐" 텍스트를 확인할 수 있습니다.
이후, 버튼을 클릭했을 때 의도했던 것처럼 "꺼짐"이 제대로 동작할까요?


image

결론적으로는 아닙니다. 브라우저는 오류 페이지를 출력하고 개발자 도구 콘솔에는 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를 사용하면 전역 객체를 참조합니다. 즉, undefinedwindow/global를 가리키게 되는데 React는 기본적으로 엄격모드이기 때문에 함수 호출시 undefined의 결과를 얻습니다.


이를 해결해주기 위해서는 this.handleClick = this.handleClick.bind(this); 코드를 constructor내부에 작성해 이벤트 핸들러 메서드의 this를 클래스 인스턴스로 고정하면 됩니다.


풀어 설명하자면 this.handleClick.bind(this)를 호출시, 기존 메서드 handleClick을 기반으로 새로운 함수 객체가 생성됩니다. 새롭게 만들어진 함수 객체는 this가 명시적으로 클래스 인스턴스를 참조하도록 고정됩니다. 이때 기억해야 하는 것은 두 가지 입니다.

  1. handleClick함수 객체의 원본이 존재하며 이는 프로토타입 객체로 메모리에 상주한다.

  2. // 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바인딩이 자동적으로 되기 때문에 위와 같은 코드를 사용하면 됩니다.