React (3) State

이종호·2022년 7월 24일
0

React

목록 보기
3/7
post-thumbnail

영상링크

React에서 state란?

데이터 변경이 있는 웹페이지를 만들고 싶다.
+1을 해주는 버튼을 누르면 count숫자가 올라가는 코드를 써보자.

let count = 0;

function handleClick() {
	count = count + 1;
  	rerender();
}

function rerender() {
	const countText = document.getElementById('content-text');
  
  counText.textContent = `현재 count ? ${count}`;
}

페이지에 있는 데이터 부분이 변경되었을때 마다 DOM에 접근하여 직접 수정해주었을것이다. 데이터가 변경된만큼 사용자가 보낸 환경도 변경되어야 하기 때문이다.

이번에는 아까와 다르게 state를 사용해서 코드를 작성해보자.

constructor () {
	...
    
    this.state = {
    	count: 0
    }
}

handleClick() {
	this.setState((state) => ({
      count: state.count + 1
    }))
}
                  
render() {
	return (
    	<>
        	<button onClick={handleClick}> +1 </button>
        	<h1>현재 count ? {this.state.count}</h1>
        </>
    ) 
}

이 코드를 이해하려면 데이터 바인딩에 대해 알고있어야 한다.

우리는 자바스크립트로 개발했을때에는 여러개의 자바스크립트 객체와 화면에 있는 데이터를 직접 일치시켜주어야 했다. 하지만 리액트가 데이터 바인딩을 대신 해주어 수고를 덜해준다.

데이터바인딩?
제공자와 소비자로부터 데이터 원본을 결합시켜 이것들을 동기화 시키는 기법
즉, 자바스크립트 객체와 화면에 있는 데이터를 일치시키는 것

그래서 우린 변경되는 데이터를 위해 리액트에서 지원하는 state를 사용할수있다. state를 사용하면 변경되는 데이터를 자동으로 rerendering할수 있게 된다.

react에서 state는 단방향으로 하향식으로 흐른다.
컴포넌트는 자신의 state를 자식의 컴포넌트에게 props로 전달해준다. 자식 컴포넌트를 설계할때 자식은 props가 누구로부터 어떤 방식으로 전달되는지 전혀 알 필요가 없다.

이때문에, state는 종종 캡슐화 라고 불린다.
state를 가진 컴포넌트 외에는 접근을 할 수 없기 때문이다.

props가 폭포라면, state는 물의 근원이라 할 수 있다.

  • props
    1. 부모로부터 전달 받는다.
    1. 읽기 전용 데이터다.
  • state
    1. 해당 컴포넌트에서 관리된다.

클래스형 컴포넌트

클래스형 컴포넌트에서 state를 사용하고, 이 state를 갱신하기위해 setState를 사용한다. 하지만 이러한 의문을 갖는다. 왜 변경되는 데이터를 표현하기위해 state를 사용할까?

이는 state가 변경됬을시 화면이 re-rendering되기 때문이다. 자세히 말하면, setState를 통해 state를 변경했을때 화면이 rerendering 된다. 또, state는 setState를 통해 변경해야 할까?

state를 직접적으로 변경했을때 새로운 state로 화면이 업데이트 되지 않기 때문이다. 이는 리액트의 lifecycle 흐름을 살펴봐야 한다.

life cycle (생명주기)

화면이 업데이트 되기 위해서는 렌더 함수가 실행되어야 한다. setState는 컴포넌트 업데이트 프로세스를 트리거 한다. 즉, setState를 사용해야 react가 state가 변경되었다는것을 알 수 있게 해준다.

업데이트 프로세스 라이프 사이클중, shouldComponentUpdate는 state에 변경사항이 있는지 비교연산을 수행한다. 이는 순수한 컴포넌트를 사용하지 않았다면 기본값으로 true를 반환하기 때문에 setState를 사용한다면 항상 rerendering이 된다.

즉, state를 직접 변경하게 되면 state는 변경되었을 수도 있다. 하지만 render함수가 실행되지 않았기 때문에 보이는 화면이 업데이트 되지는 않는다.

setState

setState의 역할은 앞서 말한대로가 전부이다.
그럼 예시코드와 함께 봐보자.

incrementCount () {
	this.setState({count: this.state.count + 1});
  
handleSomething() {
	this.incrementCount();
  	this.incrementCount();
  	this.incrementCount();
}

카운트를 1 씩 증가시키는 함수를 3번 연속으로 실행한다. 코드만 보았을때 count는 3이 되었음을 예상할 수 있다.

하지만 count에는 1이 저장되어있다.
위 코드의 함정은 무엇일까?

리액트는 컴포넌트가 re-render될 때까지 state를 갱신하지 않는다.

사실상, setState는 즉각적인 명령이 아닌 요청임을 알 수 있다.

  1. state를 바로 갱신하지 않는다.
  2. state변경사항을 대기열에 집어 넣고, 컴포넌트에게 새로운 state를 사용하기 위해 re-render를 해야 한다고 알린다.

즉, setState는 비동기 적으로 작동한다.

그렇기 때문에 연속적으로 setState를 호출해도 state가 변경되지 않았던 것이다.

this.state는 렌더링 이후에 화면에 보이는 값을 가리키는 것이다.

constructor () {
	super();
  	this.state = {
    	count: 0,
    };
}

handleClick () {
	this.setState(state => ({
    	count: state.count + 1,
    }));
  	console.log(this.state.count); // 0
}

render () {
  {this.state.count}  //1
}

위 코드를 실행했을때, 콘솔에 찍히는 값은 0이고 화면에서는 count가 1로 보여진다. 즉, state가 바로 업데이트 되지 않음을 확인할 수 있다.

incrementCount () {
	this.setState((state)=> {
      return {count: state.count + 1}
    });
}
  
handleSomething() {
	this.incrementCount();
  	this.incrementCount();
  	this.incrementCount();
}

위 코드는 어떻게 동작할까?
handleSomething함수를 보면 카운트값을 출력했을 때는 카운트값은 여전히 0이다.
하지만 리렌더링 이후 화면을 보면 카운트값은 3이 되어 있다.

이전 코드와 비교해서 setState에 객체를 넘겨주는게 아닌 함수를 넘겨주고 있다. 이를 updator 함수라고 하는데, 이를 사용하면 state가 갱신된 이후에 다시 업데이트 해주는게 보장된다.

updator 함수

this.setState((state,props)=>{
	return {count: state.count + props.diff}
});

updator함수의 첫번째 매개변수는 최신 state임을 보장해주고, 두번째 매개변수는 최신 props임을 보장한다.
그렇기 때문에 두번째 예시코드에서는 count가 3으로 렌더링 될수 있었다.

setState가 비동기로 작동하는 이유

연속적으로 state를 변경하고 변경횟수만큼 리렌더링을 진행한다면 이는 성능저하를 유발한다.

setState가 동기적으로 작동하면 어떻게 될까?
특정 이벤트가 발생했을때 부모 컴포넌트, 자식 컴포넌트 둘다 setState를 실행한다고 가정하자. 자식 컴포넌트는 setState가 실행되자마자 리렌더링 된다. 그리고 부모 컴포넌트도 setState가 실행되자마자 리렌더링 된다. 부모 컴포넌트가 리렌더링 됨으로써 자식 컴포넌트는 또 리렌더링이 된다. 즉, 자식 컴포넌트가 불필요하게 두번 렌더링이 된다.

리액트는 결국 인지성능향상을 위해 setState의 실행을 지연시키고 여러 컴포넌트를 한번에 갱신한다. 이는 배치처리라는 개념이다.

배치처리
데이터를 실시간으로 처리하지 않고 종합하여 처리하는 것

종합하면,
setState는 state를 바로 갱신하지 않을 수 있으므로
최신 state나 props를 사용하려면 setState에 updator를 넘기거나, 생명주기 함수인 componentDidUpdate를 활용하자.

함수형 컴포넌트

최근 많이 사용하는 함수형 컴포넌트. 이는 stateless 컴포넌트로도 불린다.
그런데 state가 없다면 변화하는 데이터를 어떻게 페이지에 넣어줄까?

일단, 함수형 컴포넌트에 state를 사용하지 못하는 이유는 간단하다. 일반 변수는 함수가 끝날 때 사라진다. state를 업데이트 했다고 해도 함수가 다시 실행 될때 마다 state가 다시 초기값으로 돌아가기 때문이다.

그래서 state값을 저장하기 위해 hook중 하나인 useState를 사용한다. useState를 통해 함수형 컴포넌트에서도 state를 사용할수 있게 된 것이다!

useState

const [state, setState] = useState(init);

useState는 다음 리렌더링시 배열의 첫번째 요소로 갱신된 최신 state를 반환한다. 그런데 계속 변경되는 state에 const를 선언했다. 왜 일까?

사실 setState를 통해 state를 변경하는 것이 아니다.
useState함수 아래서 접근 할수있는 변수값을 업데이트 한다. 이를 배열에 담아 반환해준다.

함수형 컴포넌트가 실행이 될때 state변수는 사실상 계속 할당이 되는것이다.

profile
Frontend

0개의 댓글