[React] state와 useState

김영서·2021년 4월 29일
2
post-thumbnail

1. hooks

React 16.8.에 새로 도입된 기능

Hooks
클래스형 컴포넌트 고유 기능인 상태(state)관리와 라이프사이클 관리 기능을 함수형 컴포넌트에서도 쓸 수 있도록 해주는 함수들을 총칭


1-1. hooks 등장 배경

"함수형 컴포넌트로 클래스 컴포넌트를 대체하기 위한 목적으로 도입"

- 리액트에서 컴포넌트란?

  • 리액트에서 컴포넌트란 웹어플리케이션, 웹페이지에서 독립적이고 재사용이 가능한 단위로(박스 단위로) 나뉘어진 블럭을 말한다.
  • 최대한 독립적이고 재사용이 가능한 작은 단위로 만들어 나가는 것이 좋다.
  • 리액트에서 컴포넌트를 만들 수 있는 방법은 클래스형, 함수형이 있다.

1) 클래스 컴포넌트

이렇게 리액트에서 제공하는 React.Component 클래스를 상속하는 클래스를 만들면 되고, UI가 어떻게 표기 될 것인지 정의하는 render() 함수를 구현해 주어야 한다. 컴포넌트 자체에 계속 기억이 되어져야 하는, UI를 표기 하기 위한 데이터가 있다면 this.state 멤버 변수에 오브젝트 형태로 데이터를 저장할 수 있다.

클래스 특성상, 클래스의 인스턴스(오브젝트)가 생성이 되면 클래스의 메소드(함수)를 아무리 많이 호출해도 클래스의 멤버 변수를 직접 수정하지 않는 한 멤버 변수는 한번 만들어 지면 계속 그 값이 유지된다.

그래서 render 함수가 아무리 많이 호출이 되어져도 this.state 에 들어 있는 데이터는 변하지 않기 때문에, 컴포넌트에서 가지고 있던 데이터를 잃어 버리지 않고 일정하게 데이터를 사용자에게 보여줄 수 있죠.

컴포넌트에서 데이터를 변경해서 UI를 업데이트 해주고 싶다면, 간단하게 this.state 만 업데이트 해주면 리액트가 알아서 render 함수를 다시 호출해 주고 브라우저에 업데이트한다.

요약해서! 리액트에서 컴포넌트를 만들려면

  • React.Component 클래스를 상속하고,
  • 데이터는 꼭 this.state에 담아 두고,
  • render() 함수에 HTML과 같은 JSX 문법을 이용해서 데이터를 어떻게 UI로 표기 할건지 정의를 해놓도록 만들기

정말 중요한 포인트!
리액트는 변경사항이 한가지의 방향으로만 흘러갑니다.
데이터가 변경이 되면 → UI가 업데이트 된다
데이터(State)가 변경이 되면 → 리액트가 render() 함수를 호출해서 UI가 업데이트 된다

2) 함수 컴포넌트

리액트에서 컴포넌트를 만들 수 있는 또 다른 방법으로는 아래와 같이 JSX를 리턴하는 함수를 정의하면 됩니다.

컴포넌트 자체에 데이터(State)가 없는 경우, 외부에서 전달받은 데이터(Props)만 보여주면 되거나, 아니면 State, Props 둘다 없는 아주 정적인 컴포넌트인 경우 굳이 클래스를 정의 하지 않고 이렇게 함수 만으로 리액트 컴포넌트를 만들 수 있다.

클래스와는 반대로 함수의 특성상, 함수를 호출할때마다 함수의 코드 블럭이 다시 실행이 되고, 그 안에 선언한 모든 로컬 변수들은 함수의 실행 컨텍스 안에서 재 정의, 값이 할당된다.

그래서 함수 안에서 State를 보관해서 일관적으로 사용자에게 보여줄 수 있는 방법이 없기 때문에 (함수가 호출될 때 모든 로컬 변수들의 값이 초기화 되어서 기존의 데이터들이 다 초기화 됨) 일반 함수형 컴포넌트에서는 컴포넌트만의 자체적인 데이터 (State)를 가질 수 없다.

- Class vs. Function

Class component의 단점

  • this 바인딩 때문에 발생하는 에러
  • 자바스크립트 상속 클래스에서 super()를 반드시 호출해줘야 하는데 이런 규칙에 따라 클래스형 컴포넌트에서도 super(props)를 호출해야 함
    한 줄 요약: class 어렵다!

1-2. React Hooks란?

훅(갈고리) 이라고 이름 지어진 이 리액트 훅은 함수형 컴포넌트에서 클래스 컴포넌트에서만 이용이 가능했던 State와 라이프 싸이클 메소드들을 이용할 수 있도록 도와주는, 함수형 컴포넌트에 리액트의 다른 기능들을 갈고리처럼 연결해주는 것들을 말한다.

리액트에서 기본적으로 제공되는 훅들은 use로 시작하는 함수들이다. 그리고 여러 컴포넌트에서 재사용 될 수 있는 로직들 이라면 스스로 정의해서도 만들 수 있다. (Custom Hooks)

State Hook
함수형 컴포넌트에서도 State를 쓸 수 있도록, 함수형 컴포넌트가 여러번 호출이 되어도, 계속 일정한 데이터를 기억하고 있는 useState()

Effect Hook
리액트의 라이프 싸이클 메소드처럼 활용할 수 있는, 원하는 데이터만 타켓으로 삼아서 그것이 변경될때마다 호출 될 수 있도록 쓸 수 있는 useEffect(): 렌더링 직후 작업 설정 가능

이외에도 useCallback, useContext, useMemo, useReducer, useRef... 등이 있다.

정적인 컴포넌트라면 순수 함수형 컴포넌트를 사용한다.
State가 필요하거나, 라이프 싸이클 메소드가 필요하다면 클래스로 구현하거나 함수형 + Hooks을 이용해서 구현이 가능하다.


2. state

컴포넌트 UI를 위한 데이터를 보관하는 오브젝트로, state라는 오브젝트를 통해서 데이터에 업데이트가 발생하면 리액트가 자동적으로 우리가 구현한 render 함수를 호출한다.

  • state는 객체이며 컴포넌트의 데이터를 state에 넣을 수 있다.
  • state의 데이터는 바꿀 수 있으며, 바꿀때는 setState()를 이용해야 한다.
  • class 컴포넌트에서 state를 쓸때는 컴포넌트의 형태가 클래스이기 때문에 this.state로 사용할 수 있다.

2-1. state와 props의 차이


State
컴포넌트 안에서 우리가 정의한 컴포넌트의 state 오브젝트
Props
컴포넌트의 재사용을 높이기 위해 컴포넌트 밖에서 주어지는 데이터

2-2. 리액트에서 state 관리의 문제점

- React 구조 = 컴포넌트의 트리 형태

컴포넌트 간 데이터 전달
React는 단방향 데이터 흐름을 갖고 있기 때문에 부모 컴포넌트에서 하위 컴포넌트로 데이터(props)를 전달할 수 있다.
만약 상하 관계가 아닌 컴포넌트 간에 데이터를 전달해야 한다면 어떻게 처리해야 할까? 부모가 같은 컴포넌트 사이라면 부모의 state를 이용할 수 있다. 부모 컴포넌트가 자신의 state를 변경할 수 있는 함수를 props를 통해 하위 컴포넌트에 내려주고 하위 컴포넌트는 해당 함수를 통해 상태를 변경하거나 공유할 수 있다.

컴포넌트 간의 거리가 먼 경우, 컴포넌트 간 데이터 전달

props를 재귀적으로 넘겨주면서 처리하면 가능은 하겠지만, 불필요한 props가 발생돼서 가독성이 떨어진다. 유지보수도 어려워진다. 이러한 문제를 해결할 수 있는 것이 바로 Redux!

- Redux에 대하여

  • Redux는 application 전체의 상태(state)를 편리하게 관리하기 위해 사용하는 라이브러리 중 하나이다.
  • 리덕스를 사용하면 스토어(Store)라는 객체 내부에 상태에 대한 데이터를 담아 관리할 수 있게 된다.
  • 스토어는 리액트 개발 프로젝트상의 상태에 대한 데이터값들을 내장하고 있다.

<Redux 참고 사이트>


3. useState를 사용한 상태관리

3-1. useState

리액트에서 기본적으로 제공되는 훅

useState()
함수형 컴포넌트에서도 State를 사용 가능케 함
함수형 컴포넌트가 여러번 호출이 되어도, 계속 일정한 데이터를 기억

  • useState는 상태를 관리하는 변수와 그 변수를 변경할 수 있는 세터 함수를 배열로 반환합니다.
  • 상태 변수는 직접 변경하는 것이 아니라 useState 함수에서 반환한 세터 함수를 이용해야 합니다.
  • useState 함수를 호출할 때 파라미터에 생성되는 상태의 초깃값을 전달할 수 있습니다. 초깃값을 전달하지 않으면 undefined로 설정되어 에러가 발생할 수 있기에 항상 초깃값을 설정하는 것이 좋습니다.

3-2. 함수형 업데이트

<예제 1>
Setter 함수를 사용 할 때, 업데이트 하고 싶은 새로운 값을 파라미터로 넣어주는 방식

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>현재 카운터 값: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
    </div>
  );
};

export default Counter;

<예제 2>
기존 값을 어떻게 업데이트 할 지에 대한 함수를 등록하는 방식

import React, { useState } from 'react';

function Counter() {
  const [count, setCounter] = useState(0);

  const onIncrease = () => {
    setCounter(currentCounter => currentCounter + 1);
  }

  const onDecrease = () => {
    setCounter(currentCounter => currentCounter - 1);
  }

  return (
    <div>
      <p>현재 카운터 값: {count}</p>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
}

export default Counter;

onIncrease 와 onDecrease 에서 setCounter 를 사용 할 때 그 다음 상태를 파라미터로 넣어준것이 아니라, 값을 업데이트 하는 함수를 파라미터로 넣어주었습니다. 함수형 업데이트는 주로 나중에 컴포넌트를 최적화를 하게 될 때 사용하게 됩니다.

0개의 댓글