+ 알아야 할 것

언제 state를 사용하는가?

local state와 application state의 개념

state의 중요한 3가지 규칙

훅 등장 이전의 function 컴포넌트와 class 컴포넌트의 차이점

state가 훅으로 옮겨갈때 어떤 훅으로 변경되는가

React에서의 이벤트 사용시 가장 중요한 개념

+ 실습

Counter 컴포넌트의 +, – 버튼을 클릭시 score가 증가, 감소 하도록 구현한다.

prerequisite

es6 class

es6 class extends

getter and setter

state 와 class component

시간에 따라 변하는 데이터가 있다면 그 상태를 state로 관리하고 state의 값을 변경하게 되면 state와 연결된 뷰가 자동으로 렌더링이 된다. 이것이 리액트의 가장 중요한 핵심 개념이다. 물론 리액트 뿐만 아니라 , 앵규러, Vue 와 같은 컴포넌트 기반에서 추구하는 핵심 개념이다.

카운터 컴포넌트에서 사용되는 score를 앞에서는 부모로 부터 물려받았지만 이렇게 부모로부터 물려받게 되면 – + 버튼을 눌러서 score 값을 변경할 수 없다. – + 버튼을 눌러서 해당 score 값을 변경할려면 이 때 score 값은 시간에 따라 변경이 되어야 하므로 props로 관리해서는 안되고 state로 관리해야 한다. 카운터 컴포넌트와 같이 특정 컴포넌트에서만 관리되는 state는 local state 라고 하고 다른 컴포넌트와 공유되어야 하는 컴포넌트는 부모에 state를 두고 공유해야 하는데 이와 같은 컴포넌트는 application 컴포넌트라고 한다.

function 으로 만든 컴포넌트는 props를 parameter로 받아들여서 JSX or React Element를 리턴하는 단순한 컴포넌트로 static한 컴포넌트다. 입력된 값을읽을 수는 있지만 동적인 상태를 유지할 수는 없다. 그래서 function component를 stateless function component라고 하고, 상태유지를 하기 위해서는 먼저 function component를 class component로 변경해야 한다.

먼저 Counter function 컴포넌트를 class 컴포넌트로 변경해보자.

score는 props로 전달 받았기 때문에 증가, 감소 등의 동적인 값을 처리할 수 없다. score를 동적인 값으로 만들기 위해서 state로 바꾸어 보자. 리액트는 화면과 관련된 라이브러리이며 화면에 만일 시간에 따라 변하는 요소가 있다면 그 요소를 컴포넌트에 저장해야하는 저장소가 필요한데 state에 저장한다고 생각하면된다. 생성자를 추가하고 this.state에 score를 추가한다.

생성자 없이 class의 속성으로 선언해도 동일한 결과를 얻는다. 생성자에서 this로 속성을 추가하는것은 es5의 생성자함수와 동일하다. 클래스 바로 아래에 속성으로 추가하는 것은 es6의 퍼블릭 속성인데, 초기 es6 문법에 없다가 뒤늑세 stage4에서 추가된 문법이다. 클래스 바로 아래에 var, let, const 같은 것이 없이 선언되면 클래스의 퍼블릭 속성이라고 기억하자.

React devtools에서 Counter 컴포넌트에 props 이외에 state가 추가된것을 확인하고 임의의 값으로 변경을 해보자.

click event

+ 버튼을 클릭시 score를 증가시키는 이벤트를 추가해보자. 먼저 incrementScore라는 함수를 추가한다. 그리고 JSX에서 클릭 이벤트를 추가한다. onClick 다음에 JSX expression을 추가하는데 함수를 호출하는게 아니라 함수 레퍼런스를 넣어야 한다.

리액트는 명령행 프로그래밍이 아니라 선언형 프로그래밍이라고 하였다. 클릭을 하면 클릭시 실행할 펑션을 정해놓는 것이다. 문법적으로는 function definition 또는 function expression 이 들어가야 되고 익명함수면 arrow 함수로 간편하게 작성도 가능하다.

단, 여기서 주의할점은 펑션을 정의해 놓기 때문에 이 펑션이 객체에 바인딩되지 않고 따로 떨어져 나오게 된다. 그래서 펑션내에 만일 this를 사용하게 되면 this가 객체를 가르키니 않고 글로벌 this가 되어 버리기 때문에 이것을 해결하기 위해서 bind() 문법을 사용하거나 아니면 es6에서온 arrow 펑션을 사용하게 되면 arrow 펑션안에 사용된 this는 lexical this가 되기 때문에 bind 없이 사용가능하게 된다.

incrementScrore 함수안에는 this.state.score = this.state.score + 1 과 같이 상태를 업데이트를 해서는 안된다. 상태를 업데이트할때는 반드시 setState 함수로 업데이트해야 UI가 re-render된다는것을 기억하자.

하지만, this에서 에러가 발생한다. 여기서의 this는 생성자 함수내에서의 this 이기 때문에 자기 자신을 가르킨다. 하지만 클릭시 콜백 함수로 this가 넘어가게되면 펑션만 넘어가지 this가 전달되지 않기 때문에 this 값을 잃어버리게 된다. 따라서 이를 해결하기 위해서는 아래와 같이 명시적으로 this값을 bind하는 방법이 있다. 

혹은 생성자 안에서 아래와 같이 바인딩할 수 있다.

두번째는 arrow function을 사용하는 방법이다 arrow function 내에 사용된 this는 lexical this라고 하여 자동적으로 원래 가르키고자하는 scope의 this를 참조하게 된다. 아래와 같이 arrow function을 사용하여 increment 함수표현식을 사용하면된다.

decrementScore 함수도 동일하게 작성하여 보자

updating based on prev state

this.state.score + 1 로 상태를 업데이트하는 것은 비동기적으로 렌더링 된다. 즉, 여러번의 이벤트가 일어나면 이 상태가 언제 실행되는지 보장할 수 없는 불일치성 문제를 일으킬 수 있다. 그래서 일치성을 보장하기 위해서는 이전 상태에 기반하여 상태를 업데이트하여야 한다. setState는 이전상태 값에 기반한 콜백 펑션을 제공해준다.

또한 setState에 지정된 state는 merge 된다. 만일 state에 a,b,c,d,e 다섯가지의 상태가 정의되어있다면 setState에 c만 상태를 갱신했다면 a, b, c, d, e 상태에서 c만 merge가 일어나고 나머지 상태는 그대로 있게 된다는 얘기이다.

+ 요약

  • 시간에 따라 변하는 데이터는 state에 보관한다.
  • 이벤트 우측에는 함수 선언문이 놓여야 한다.
  • state 규칙1 – state를 변경하는 방법은 setState 밖에 없다. setState로 변경해야만 UI가 렌더링 된다.
  • state 규칙2 – setState에 state를 수정하면 merge (오버라이팅) 된다.
  • state 규칙3 – setState는 비동기로 처리되기 때문에 이전 상태에 따라 현재 상태를 업데이트할 시에는 이전 상태를 받아서 처리해야 한다.

+ note

ToDo

먼저 TodoApp 컴포넌트를 클래스 컴포넌트로 전환한다음 todos 배열을 state로 관리하시오.

+ quiz