React 라이브러리

4. Js 동기, 비동기 처리 : setState 의 문제점 및 해결

문정훈 2021. 10. 31. 17:05

0. 헷갈릴 내용 정리

class ClassA {
   name = 'kim';
   
   obj  = {
    name :'Lee',
    p : this.name,
    f: function() {
      console.log(this.name); // Lee
      console.log(this.p); // kim
    }
   }
}

var c1 = new ClassA();
c1.obj.f();

클래스를 선언하고 클래스 안에서 프로퍼티로 obj를 선언하였다. 

헷갈릴 내용이 이 obj 안에서 this 사용이다. 

 

객체가 만들어지는 과정은 위 전역 코드가 '평가'되어서 객체 리터럴이 전역 코드 안에  일종의 동륵이 되는데

이때 객체의 프로퍼티들의 값을 하나하나 넣게 된다. 먼저 name: 'Lee'를 저장하고 p : this.name을 저장하려고하는데 이때 this는 객체 안에 선언된 this가 맞지만 아직 객체 리터럴이 만들어지지 않았기 때문에 this는 ClassA를 바인딩하게 된다. 따라서 이때 p의 값은 'Kim'을 저장하게 된다. 

그리고 객체 리털럴의 함수 f는 객체 리터럴 안에 동륵이 되고 f의 평가와 실행은 함수의 호출이 일어나야 진행된다. 

 

객체 안에 선언된 함수도 함수가 호출될 떄 '평가' 과정이 진행되므로 this 바인딩도 이때 일어난다.

즉 객체 리터럴이 다만들어지고나서 나중에 객체의 메소드를 호출할때 그때 메소드 안의 this가 바인딩된다는 것임

 

한줄 요약=>

클래스 안에 선언된 객체 리터럴의 프로퍼티를 선언하는 코드에서 값을 선언하는데 this를 사용하면 그 this는 객체 리터럴에 바인딩 되지 않고 클래스에 바인딩된다 

 

 

 

1. setState 문제점 발생

class ClassA extends React.Component {
  constructor(props) {
     super(props);
     this.state = {
        count : 0;
     }
  }
  
  increaseCount() {
    this.setState({count : this.state.count + 1});
    this.setState({count : this.state.count + 1});
    this.setState({count : this.state.count + 1});
  }
}

var c1 = new ClassA();

c1.increaseCount();

 

 

위 코드를 보면 state의 count값이 3이 될 것이라 기대하지만 실제로 increaseCount()를 세번 호출했음에도 count의 값은 1이 된다.

그 이유는 setState가 js에서 비동기식 처리가 되기 때문이다.  자세히 과정을 설명하면

 

▶ 과정1) 

위 예시의 전체 전역 코드가 '평가'되고 '실행' 된다. 그리고 비동기식 처리 메소드 c1.increaseCount(); 를 호출하게 된다. 

 

 

▶ 과정2) 

c1.increaseCount()가 호출되어 메소드 안에서 첫 번째 setState를 호출하게 되면 setState 함수의 '평가'가 진행된다.

따라서 콜 스택에 setState 함수의 실행 컨텍스트가 등록된다. (평가의 첫 시작이 실행 컨텍스트를 콜 스택에 push하는 것임)

그리고 setState함수의 '평가'가 끝나고 '실행'을 알려고 할려고하는데 그때 브라우저는 이 setState 메소드를 콜 스택에서 pop시키고 이벤트 큐로 옮겨 실행 대기를 시킨다. 그리고 두 번째 setState를 호출한다. 

setState 호출이 3번이므로 이 과정을 3번한다.

 

정리=>

비동기식 처리 메소드 setState()가 콜 스택->이벤트 큐로 가는데 setState() 메소드의 '평가'는 이미 끝났고 실행하는 것을 잠시 대기시키는 것이다. 그 대기를 이벤트 큐에서 대기하도록 하고, 콜 스택이 비게 되면 이벤트 큐의 비동기 메소드를 '실행'하는 것이다.

 

 

▶ 과정3)

이벤트 큐로 이동한 세 개의 setState 함수는 이미 '평가'과정은 모두 끝났다.

아직 '실행'은 하지 않음

※ this 바인딩은 '평가'과정에서 바인딩된다.

세번의 setState는 모두 평가만 진행된 것이므로 this 바인딩만 되었고 아직 setState의 '실행'단계에서 진행 하는 count = count + 1 내용은 하지 않았음을 말하는 것임.

 

정리=>

첫 setState를 호출할 때 this.state.count의 값을 가지기 때문에 0이다.

근데 count = count + 1 작업을 수행하는 setState의 '실행' 과정을 하기도 전에 콜 스택에서 setState가 pop되고 이벤트 큐로 실행 대기가 된 후 두 번째 setState가 또 호출 되므로 두 번째 setState에서도 this.state.count의 값은 0을 그대로 가진는 것이다.

 

 

▶ 과정4)

전역 코드가 콜스택에서 pop되고 이벤트 큐에 있는 세번의 setState가 이제 차례로 실행될려고 하는데 

세 메소드 모두 this.count는 0을 가지므로 세번 다 그냥 0+1, 0+1, 0+1 똑같은 작업을 수행하여 결국 count는 1이 되는 것이다. 

 

 

 

2. setState 문제점 해결

class ClassA extends React.Component {
  constructor(props) {
     super(props);
     this.state = {
        count : 0;
     }
  }
  
  increaseCount() {
    this.setState((prevState)=>{ return {count : prevState.count +1} });
    this.setState((prevState)=>{ return {count : prevState.count +1} });
    this.setState((prevState)=>{ return {count : prevState.count +1} });
  }
}

var c1 = new ClassA();

c1.increaseCount();

setState의 문제점을 해결하는 방법 중 하나는 위와 같이 함수를 지정하는 것이다. 

setState의 매개변수에는 state를 업데이트할 객체를 선언해도 되지만 위와 같이 콜백함수를 가진 메소드를 위 형식과 같이 지정해도 된다. 

prevState 의 값으로 state 정보가 전달된다. 

 

위 방식이 어떻게 문제점을 해결하는지를 설명한다. 

 

▶ 과정1)

setState가 호출되어 '평가'만 진행되고 테스크 큐로 실행 대기가 된다 하였다.

핵심은 setState와 같은 함수가 '평가'되면 setState의 매개변수로 지정한 화살표 함수는 setState의 내부에 있는 함수인데, (이것을 M이라 할 때) 이 M은 '평가'가 안된다.

다시말해서

setState의 매개변수로 지정한 화살표 함수 M은 setState의 평가만으로는 M 함수의 평가는 되지 않고, setState 내부의 메소드로써 일종에 등록만 된다. 그럼 언제 M이 '평가'되는가 하면

이벤트 큐에서 대기 중인 setState가 콜 스택으로 브라우저에 의해 push되어 setState가 '실행'을 시작할 때 setState안에서 M을 호출할 때 그때 발생한다. 

 

 

▶ 과정2)

첫 setState가 이벤트 큐에서 콜 스택 영역으로 push되어 실행을 시작한다. 

아마 setState의 내부에서는 setState의 매개변수로 지정한 M이라는 함수를 호출하여 리턴 값으로

{count : prevState.count +1}  객체를 리턴 받아 count +1을 처리할 것이다. 

 

첫 번째 setState가 종료되고 콜 스택에서 pop되면 두 번째 setState가 이벤트 큐에서 콜 스택 영역으로 push되어 실행을 시작한다.

이때 M이 평가 되어 state에 접근하기 때문에 count의 값은 업데이트된 1의 값을 가지게 되고 count + 1을 진행한다. 

 

▶ 과정3)

위 두 과정의 설명으로 setState의 콜백함수를 가진 메소드를 전달하면 비동기 함수여서 발생하는 위 문제점을 해결할 수 있게 된다. 

 


끝..

- 함수A 안에 선언된 객체 리터럴은 이 함수를 호출하여 함수의 '평가'과정만으로 객체가 생성되고 객체 안에 선언된 this가 결정된다. 

 

- 하지만 함수A 안에 함수 B가 선언되어 있을 때 함수 A의 '평가' 만으로는 함수 B의 '평가'가 진행되지 않고

(B가 A안에서 등록만 됨) 

함수 A가 '실행' 되어서 함수 B가 호출 될 때 함수 B가 '평가'되고 '실행'된다.  

 

setState는 함수 A에 해당하고 setState의 위와같은 문제점은 객체 리터럴의 '평가'시점과

B 함수의 '평가' 시점이 다르기 때문에 전자로 인해서 문제점이 발생하고 후자로 문제점을 해결하였다.