2 분 소요

React를 공부하고 있는데, Render() 함수 안에서 사용되는 콜백함수를 밖에 따로 정의해주려는데, 화살표 함수가 아닌 일반 함수형으로 선언을 하게 되면 this가 달라져 동작에 문제가 생기는 것을 발견했다. 화살표 함수를 사용해야만 하는 이유를 알아보자.

⚠️ 변수에 익명 함수의 형태로 함수를 선언했을 때 발생하는 오동작에 대해서 이야기하고자 하며, 자바스크립트와 같이 일반 함수 선언문을 사용하는 것은 리액트 클래스 컴포넌트 내부에서는 지원되지 않는다.

📘자바스크립트 스코프의 특징

this의 바인딩에 대해서 알기 전 스코프에 대해서 먼저 알아봐야한다.

대부분의 언어는 블록 레벨 스코프, 즉 같은 블록 내의 코드에서만 접근이 가능하다는 특징을 가진다. 자바스크립트는 함수 레벨 스코프를 따른다는 차이점이 있는데, let 키워드를 사용하면 블록 레벨 스코프를 사용할 수 있다.

ECMAScript 6부터는 var 대신 let과 const의 사용을 권장하기에 사실상 자바스크립트도 블록레벨 스코프를 가졌다고 봐도 될 것 같다.

var x = 0;
{
  var x = 1;
  console.log(x); // 1
}
console.log(x);   // 1

let y = 0;
{
  let y = 1;
  console.log(y); // 1
}
console.log(y);   // 0

📘 암묵적 전역

전역 스코프와 지역 스코프에 대해서는 전공자라면 흔히 아는 부분이지만, 암묵적 전역에 대해서는 처음 알게 되어 짚고 넘어간다.

console.log(x); // undefined
console.log(y); // ReferenceError: y is not defined

var x = 10; // 전역 변수

function foo () {
  // 선언하지 않은 변수
  y = 20;
  console.log(x + y);
}

foo(); // 30

전역 변수 x는 호이스팅이 발생한다. 자바스크립트 엔진은 전혀 선언하지 않은 y를 window.y로 해석하기 때문에 전역 객체의 프로퍼티로 취급되는데, 이를 암묵적 전역(implicit global)이라고 한다.

전역 변수가 아니라 전역 프로퍼티인 y는 호이스팅이 발생하지 않는다. 그리고 프로퍼티이므로 delete연산자로 삭제할 수 있다.

📘 렉시컬 스코프

프로그래밍 언어가 상위 스코프를 결정하는 방식은 두 가지가 있다.

1) Dynamic Scope

함수를 어디서 호출하였는지에 초점을 맞춘다.

2) Lexical Scope

함수를 어디서 선언하였는지에 초점을 맞춘다.

var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // 1
bar(); // 1

자바스크립트를 포함한 대부분의 언어는 렉시컬 스코프를 따른다. 따라서 위의 결과는 둘 다 1을 출력할 것이다.

📘 함수 호출 방식과 this 바인딩

자바스크립트의 경우 함수를 호출할 때 this에 바인딩할 객체가 동적으로 결정된다. 그런데 내부함수는 일반 함수, 메소드, 콜백함수 어떤 방법으로 선언되었든 간에 this가 무조건 전역객체를 바인딩한다. 따라서 render() 함수 내부에서 콜백함수를 부를 때, 그리고 코드의 간결성을 위해 함수를 밖에 따로 선언할 때에도 일반 함수의 형태로 선언을 하고 this로 state의 값을 바꾸려 하면 에러가 발생하는 것이다.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar() {
      console.log("bar's this: ",  this); // window
      console.log("bar's this.value: ", this.value); // 1
    }
    bar();
  }
};

obj.foo();

그런데 화살표 함수는 dynamic이 아닌 lexical 방식으로 this를 바인딩하기 때문에, 화살표 함수로 선언을 해주면 우리가 원하는 대로 동작을 하게 된다. 따라서 render() 함수에서 데이터를 조정할 때 사용하는 함수는 화살표 함수를 사용해야 한다.

참고


https://poiemaweb.com/js-scope#7-렉시컬-스코프

https://poiemaweb.com/js-this

https://hsp0418.tistory.com/148

댓글남기기