All Articles

리액트 상태값(state)이란?

리액트에서 상태값(state)이란?

컴포넌트 내 변경 가능한 데이터 저장소. UI(엘리먼트)에 반영하기 위해 유지해야할 값 묶음이다. 리액트 컴포넌트에 저장한 데이터가 변화하면 UI가 자동으로 갱신된다.

리액트 개발의 핵심은 상태값을 효율적으로 관리하는 것, 그리고 상태값에 따라 화면이 불필요하게 업데이트되지 않도록 관리하는 것이다.

각 컴포넌트 안에서만 사용하는 값은 해당 컴포넌트 안에서 생성하고 갱신한다. 여러 컴포넌트에서 사용하는 값은 별도 공간에서 생성하고 갱신한다.

상태값 관리는 getter/setter 함수로 하고, UI 갱신 문제 등이 있으니 직접 변경하는 것은 지양하자.

상태값 변경 예시

클래스형 컴포넌트에서는 항상 this.state 객체를 통해 상태값에 접근한 뒤, this.setState 함수로 상태값을 변경해야 한다.

import React from 'react';
import 'App.css';

class Counter1 extends React.Component {
  state = {
    value: this.props.initialValue,
  };  // 새로운 컴포넌트가 만들어질 때마다 수행되는 부분. 초기화한 값(<Counter1 initialValue={ 10 } />)을 받아온다(props)

  onClick = () => {
    // this.state.value += 1;  이렇게 상태값을 직접 변경해주면 안됨

    const value = this.state.value + 1;
    this.setState({ value });  // 파이썬으로 따지면 {"value": value}, JS 다른 표기법으로는 {value: value}와 같다. JS에서 키-값 변수가 같으면 생략 가능하므로 {value}가 된다
  }  // 이벤트 리스너 함수 선언은 <함수명> = () => {}으로 해준다

  render() {
    const { value } = this.state;  // state에 있는 값을 가져와 변수 value에 할당
    return (
      <div>
        Counter1: { value }
        <button onClick={this.onClick}>+1</button>
      </div>
    )
  }
}  // 그 외에는 함수명() {}으로 해준다

function App() {
  return (
    <div>
      <Counter1 initialValue={ 10 } />
    </div>
  );
}

export default App;

setState() 함수가 함수를 받는 경우

setState() 함수는 비동기로 동작하며 변경할 특정 상태값이 담긴 객체를 지정하거나 함수를 지정한다. 함수를 지정하면 호출되기 직전 상태값(보통 prevState로 명명)을 받는다.

그렇다면 객체를 받아오는 setState() 함수를 여러 번 호출하면 value값이 호출 횟수만큼 올라갈까? 결론부터 말하면 아니다.

setState() 함수는 비동기로 동작하기 때문에 setState()가 몇 번 있든 value는 1만 증가한다.

import React from 'react';
import 'App.css';

class Counter1 extends React.Component {
  state = {
    value: this.props.initialValue,
  };

  onClick = () => {
    this.setState({ value: this.state.value + 1 });
    this.setState({ value: this.state.value + 1 });
    this.setState({ value: this.state.value + 1 });
  };  // 이렇게 3번 호출해도 value는 1만 증가함

  render() {
    const { value } = this.state;
    return (
      <div>
        Counter1: { value }
        <button onClick={this.onClick}>+1</button>
      </div>
    );
  }
}

function App() {
  return (
    <div>
      <Counter1 initialValue={ 10 } />
    </div>
  );
}

export default App;

버튼 1번 클릭할 때 3씩 올라가게 하려면 setState()에 함수를 지정해야 한다.

import React from 'react';
import 'App.css';

class Counter1 extends React.Component {
  state = {
    value: this.props.initialValue,
  };

  onClick = () => {
    this.setState((prevState) => {
      const { value } = prevState;
      return { value: value + 1 };
    });  // prevState(직전 상태값)를 파라미터로 받아서 value에 1을 더해준다

    this.setState((prevState) => {
      const { value } = prevState;
      return { value: value + 1 };
    });

    this.setState((prevState) => {
      const { value } = prevState;
      return { value: value + 1 };
    });
  };

  render() {
    const { value } = this.state;
    return (
      <div>
        Counter1: { value }
        <button onClick={this.onClick}>+1</button>
      </div>
    );
  }
}

function App() {
  return (
    <div>
      <Counter1 initialValue={ 10 } />
    </div>
  );
}

export default App;

상태값을 어디에 저장하고 관리할 것인가?

컴포넌트 내부

각 컴포넌트 객체 단위로 상태값을 유지하고 하위 컴포넌트에 속성값으로 전달할 수도 있다. 단 컴포넌트 수가 많아지고 계층이 복잡해지면 최상위 컴포넌트에서 최하위 컴포넌트로 상태값, 속성값 전달하기에 비효율적이다. 단지 전달만을 위해서 어떤 자식 컴포넌트를 거쳐가는 경우도 있기 때문.

컴포넌트 외부

컴포넌트 외부에 상태값 저장소를 둔다. 여러 컴포넌트가 같이 쓸 상태값(로그인 정보 등)을 관리한다.

Redux가 상태값을 관리하는 법

setter 함수 말고 dispatch 함수를 제공한다. State Reducer 패턴을 사용한다.

// dispatch를 호출할 때 항상 state가 같이 넘어온다. 이 자체가 직전 상태값이다
function dispatch(action, state) {
  const { type, payload } = action;
  if ( type == 'INCREMENT' ) {
    const { value } = payload;  // 아래 선언한 payload에서 value를 읽어온다
    return {
      ...state,
      value: state.value + value,
    }  // 여기에서 state가 변화한다
  }
  else {
    return state;
  }  // type이 INCREMENT가 아니라면 직전 상태값을 그대로 반환한다
}

// 여기서 'INCREMENT'는 임의로 정의한 값이다
// 마찬가지로 payload라는 키 이름도 임의로 정의된 이름이라 다른 키 이름으로 써도 되지만 보통 payload로 쓴다
const action = {type: 'INCREMENT', payload: {value: 1}};
dispatch(action);

dispatch({
  type: 'INCREMENT',
  payload: { value: 3 },
});