React의 핵심 개념: State 파해치기!

chaen·2024년 4월 7일

REACT / NEXT.js

목록 보기
7/22
post-thumbnail

❓ State란?

State는 상태라는 뜻입니다. 상태는 어떤 사물의 형편이나 모양을 일컫는 말로 일상 생활에서도 흔히 사용합니다.

상태는 전구와 스위치에 빗대어 생각하면 쉽게 이해할 수 있습니다. 스위치를끄면 전구에 불이 들어오지 않는데, 이를 ‘소등 상태’라고 할 수 있습니다. 반대로 스위치를 켜면 전구에 불 이 들어오며 이를 ‘점등 상태’라고 할 수 있습니다.

이처럼 State란 현재 가지고 있는 형태나 모양을 정의하는 값이면서, 변화할 수 있는 동적인 값을 뜻합니다.
리액트의 모든 컴포넌트들은 State 값을 가질 수 있습니다.
전구의 상태와 리액트 컴포넌트의 State는 매우 유사합니다. 전구의 상태가 상태 변화에 따라 점등 또는 소등으로 변하는 것처럼 리액트 컴포넌트 또한 State 값에 따라 다른 결과를 렌더링합니다.

이때 컴포넌트가 다시 렌더링되는 상황을 리렌더(Re-Render), 리렌더링(Re-Rendering)이라고 부릅니다. 또한, 전구의 소등 상태, 전구의 청소 상태, 전구의 색상 등 State 값은 여러 개를 가질 수 있습니다.

💻 State 생성, 변경하기

import { useState } from "react"; //①

function Body() {
  const [count, setCount] = useState(0); //②

  const onIncrease = () => { //③
    setCount(count + 1);
  };

  return (
    <div>
      <h2>{count}</h2>
      <button onClick={onIncrease}>+</button> 
    </div>
  );
}
export default Body;
  1. 우선, useState는 리액트가 제공하는 State를 만드는 함수입니다. 따라서 useState를 react 라이브러리에서 불러와야 합니다.

  2. useState를 호출하여 2개의 요소가 담긴 배열을 반환합니다. 첫 번재 요소는 현재 상태의 값을 저장하고 있는 State 변수이며, 두 번째 요소는 State 변수의 값을 변경하여 상태를 업데이트하는 set 함수입니다. 마지막 useState의 괄호 안에 담긴 값은 초깃값으로 현재는 0이 설정되었습니다.

  3. 언제 set 함수를 실행할지를 결정하는 이벤트 핸들러를 만듭니다. 현재는 button이 클릭될 때마다 setCount 함수가 호출되어, 인수로 count에 1 더한 값을 전달합니다

그런데, 왜 단순히 변수를 쓰지 않고 state로 관리하는 걸까요? 아래처럼 코드를 작성하면 어떻게 될까요?

function Body() {
  let count = 0;
  return (
    <div>
      <h2>{count}</h2>
      <button onClick={() => {count += 1}}>+</button> 
    </div>
  );
}

이렇게 작성했을 때도 실제 count 값은 계속 증가할 겁니다. 하지만 state 가 아니기 때문에 화면이 리렌더링 되지 않아요. 변수의 값을 아무리 많이 수정한다고 해도 단순한 변수는 렌더링이 다시 일어나지 않기 때문에 화면의 요소를 수정하기 위해서는 state를 써야 하는 겁니다.

State로 Props 관리하기

import { useState } from "react";

const Bulb = ({ light }) => {
  return (
    <div>
      {light === "ON" ? (
        <h1 style={{ backgroundColor: "orange" }}>ON</h1>
      ) : (
        <h1 style={{ backgroundColor: "gray" }}>OFF</h1>
      )}
    </div>
  );
};

function App() {
  const [light, setLight] = useState("OFF");]
  return (
    <div>
      <Bulb light={light} />
      <button
        onClick={() => {
          setLight(light === "ON" ? "OFF" : "ON");
        }}>전원</button>
    </div>
  );
}

App 컴포넌트의 전원 버튼을 눌러 light를 바꾸면 그에 따라 Bulb의 <h1>이 변경되어 화면에 반영됩니다. 여기서 알 수 있는 중요한 사실은, 자식 컴포넌트는 부모에게서 전달된 props의 값이 변겅되면 자신의 state가 변경되지 않아도 리렌더링된다는 것입니다.

컴포넌트가 리렌더링되는 경우

  1. 본인의 state가 변경될 경우
  2. 부모로부터 받은 props가 변경될 경우
  3. 부모 컴포넌트가 리렌더링될 경우

그런데 이렇게 될 경우 App 컴포넌트에 다양한 state 값이 바뀔 때마다 관련 없는 자식 컴포넌트들이 모두 리렌더링되어 성능에 좋지 않은 영향을 끼치게 됩니다. 따라서 아래처럼 요소들을 기능 단위로 각 컴포넌트에 분리해야 합니다.

//Bulb.jsx
import { useState } from "react";

const Bulb = () => {
  const [light, setLight] = useState("OFF");

  console.log(light);
  return (
    <div>
      {light === "ON" ? (
        <h1 style={{ backgroundColor: "orange" }}>ON</h1>
      ) : (
        <h1 style={{ backgroundColor: "gray" }}>OFF</h1>
      )}

      <button
        onClick={() => {
          setLight(light === "ON" ? "OFF" : "ON");
        }}
      >
        {light === "ON" ? "끄기" : "켜기"}
      </button>
    </div>
  );
};

export default Bulb;

// App.jsx
import Bulb from "./components/Bulb";

function App() {
  return (
    <>
      <Bulb />
    </>
  );
}

export default App;

💻 State로 사용자 입력 관리하기

<Input>, <Select>, <Textarea> 태그 등은 로그인, 회원 가입, 게시판, 댓글 등의 기능에서 자주 사용되며 리액트에서 State를 이용하면 이러한 기능을 효과적으로 처리할 수 있습니다.

import { useState } from "react";

// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개

const Register = () => {
  const [name, setName] = useState("이름");
  const [birth, setBirth] = useState("");
  const [country, setCountry] = useState("");
  const [bio, setBio] = useState("");

  const onChangeName = (e) => {
    setName(e.target.value);
  };

  const onChangeBirth = (e) => {
    setBirth(e.target.value);
  };

  const onChangeCountry = (e) => {
    setCountry(e.target.value);
  };

  const onChangeBio = (e) => {
    setBio(e.target.value);
  };

  return (
    <div>
      <div>
        <input
          value={name} //초기값
          onChange={onChangeName}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          value={birth}
          onChange={onChangeBirth}
          type="date"
        />
      </div>

      <div>
        <select value={country} onChange={onChangeCountry}>
          <option value=""></option>
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
        </select>
        {country}
      </div>

      <div>
        <textarea value={bio} onChange={onChangeBio} />
      </div>
    </div>
  );
};

export default Register;

차례로 이름과 생일을 묻는 <Input> 태그 두 개, 국적을 묻는 <Select>, 바이오의 소개를 적을 수 있는 <Textarea>를 사용하였습니다. 이 입력값들은 입력과 동시에 이벤트 핸들러가 실행되어 state 값이 저장됩니다.

💻 객체를 사용하여 사용자 입력 효율적으로 관리하기

import { useState } from "react";

const Register = () => { // 간단한 회원가입 폼
  const [input, setInput] = useState({
    name: "", // 1. 이름
    birth: "", // 2. 생일
    country: "", // 3. 국적
    bio: "", // 4. 자기소개
  });

  const onChange = (e) => {
    setInput({
      ...input, // 수정한 요소 외의 나머지를 그대로 저장
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <div>
        <input
          name="name"
          value={input.name}
          onChange={onChange}
          placeholder={"이름"}
        />
      </div>

      <div>
        <input
          name="birth"
          value={input.birth}
          onChange={onChange}
          type="date"
        />
      </div>

      <div>
        <select
          name="country"
          value={input.country}
          onChange={onChange}
        >
          <option value=""></option>
          <option value="kr">한국</option>
          <option value="us">미국</option>
          <option value="uk">영국</option>
        </select>
      </div>

      <div>
        <textarea
          name="bio"
          value={input.bio}
          onChange={onChange}
        />
      </div>
    </div>
  );
};

export default Register;

비슷하게 생긴 각 state와 이벤트 핸들러를 하나로 통합할 수 있습니다. 객체를 사용하여 state를 묶고, 이벤트 핸들러에서 ...input을 사용하여 기존 것만 업데이트하고 나머지는 그대로 저장하도록 설정합니다. 바뀐 요소의 경우 각 태그에 붙은 name 속성으로 알아봐 저장할 수 있도록 합니다.

이렇게 하면 효율적으로 여러 가지 비슷한 이벤트 핸들러를 한 번에 관리할 수 있습니다.


🔗참고 자료
한 입 크기로 잘라먹는 리액트

0개의 댓글