최근에 회사에서 작업을 하다가 이해가 안되는 상황을 맞이했었다.
우리 회사는 상태관리 도구로 Mobx를 사용하기에, 자연스럽게 Mobx Store, Model을 위한 Class를 자주 사용하고, class내의 프로퍼티들을 가리킬수있는 this도 자주 사용하게 되었다.
// DialogStore.ts
class DialogStore {
dialogNum = 1
constructor(){
makeAutoObservable(this)
}
goDialogNext(){
this.dialogNum += 1;
}
}
// Dialog.tsx
import dialogStore from "./DialogStore"
<DialogTypeSelection
onClose={onClose}
open={isOpen}
handleDialogNext={???}
/>
위의 코드에서 Dialog.tsx
는 여러 페이지의 dialog를 모두 가지고 있는 부모 컴포넌트고, 각 자식 dialog에 dialogNext(다음페이지로 이동)기능을 핸들러로 넘겨야 하는 상황이었다.
일반적으로 핸들러를 넘기듯이, 단순히 함수를 넘기면 될 것 같다고 생각했었고
<DialogTypeSelection
onClose={onClose}
open={isOpen}
handleDialogNext={dialogStore.goDialogNext}
/>
그리하여 위와 같이 코드를 작성하게 되었다.
항상 핸들러를 넘길때 인자가 있으면 화살표 익명함수로 감싸서 내려줬을뿐, 인자가 없으면 그냥 쌩으로 핸들러를 내려줬었기 때문이다.
그러나...
goDialogNext
메서드 내부의 this
가 undefined
라는 에러를 코드가 뿜어냈다.
이해가 가지 않았다. this
에 대해 공부할때, 객체의 메서드에 있는 this
는 해당 객체를 가르키는 것이라고 들었기 때문이다. 그러니, 무조건 this
는 dialogStore
그 자체를 가르키는게 당연하다고 생각했었다.
그러나 this바인딩은, 호출될때 정해지는 것이고 위의 콜백 방법(메서드를 직접 다른곳에 전달)으로는 this가 인스턴스에 바인딩 되지않고 끊어진다는 것이었다.
Class를 자주 사용해보지 않아, this바인딩의 위험성에 대해 모르고 그대로 메서드를 전달했던것이 화근이었던 것이다.
bind
를 활용한 명시적 bind<DialogTypeSelection
onClose={onClose}
open={isOpen}
handleDialogNext={dialogStore.goDialogNext.bind(dialogStore)}
/>
위와 같이 bind
를 활용하여 명시적으로 인스턴스를 참조하도록 바인딩을 걸어주면 this는 dialogStore를 가르키게 된다
<DialogTypeSelection
onClose={onClose}
open={isOpen}
handleDialogNext={()=>{dialogStore.goDialogNext()}}
/>
함수로 메서드를 한번 감싸서 자식에 전달하면 this바인딩에 깨지지 않고 잘 전달된다.
Class와 this공부에 소홀했었는데, 실무에서 오류를 겪으니 제대로 공부해야겠다고 다시한번 생각하게 되었다
this 바인딩 정말 무섭네요 ㄷㄷ 조심해야겠어요