All'alba vincerò

At dawn, I will win!

React

[React] 컴포넌트 상태(State / useState())

나디아 Nadia 2024. 8. 5. 22:02

✅ 컴포넌트 종류

  • Stateless Component
    (Presebtational Component)
    표현 목적의 컴포넌트(마크업, 스타일링)

 

  • Stateful Component
    (Container Component)
    : 상태가 있는(변경되는) 컴포넌트

 

 

 

✅ 리액트가 다루는 데이터 타입

1. 컴포넌트 속성(props): 읽기 전용

  • 수정 ❌

 

2. 컴포넌트 상태(states)읽기 / 쓰기

  • 컴포넌트 트리(렌더 트리 A)➡️ 화면 렌더링
  • 컴포넌트 트리(렌더 트리 Z) ➡️ 화면 업데이트
  • 시간의 흐름에 따라 화면 갱신(업데이트)👉 상태(state) 사용

 


📌 State(상태)

: 컴포넌트 내부의 데이터를 기억하는 것

 

 

 

 

📌 useState

: 컴포넌트에 state 변수를 추가할 수 있는 React Hook

const [state, setState] = useState(initialState)
  • state: 저장한 값을 가진 state 변수
  • setState: state 변수를 업데이트하고, React에 컴포넌트를 다시 렌더링하도록 유발하는 setter 함수
     
  • 상태와 상태 변경 함수를 배열로 반환
  • useState를 호출 👉 React에 이 컴포넌트가 무언가를 기억하기를 원한다고 말하는 것이다.
  • 함수 컴포넌트에서 사용

 

 

✨ 리 렌더링(Re-Rendering)이 발생하는 상황

  1. 자신의 state값이 변경되면
  2. 부모에게 제공받은 props값이 변경되면
  3. 부모 컴포넌트가 리렌더링되면

 

 

  • useState 훅의 업데이트 함수는 상태를 합성하지 않고 덮어쓴다.
    객체 또는 배열 타입의 데이터를 상태로 관리할 경우, 데이터를 직접 합쳐 주어야 함(spread syntax)
const handleUpdateFormData = (e) => {
  const { name, value } = e.target;

  const nextFormData = {
      // formData가 없으면 content를 입력하는 순간
      // title 내용이 없어져 버림
    ...formData,
    [name]: value,
  };

  setFormData(nextFormData);
};

 

 

 

✅ useState 사용 순서

  1. 상태 & 상태 업데이트 정의
  2. 이벤트 핸들러 함수에서 상태 업데이트
  3.  파생된 상태
    - 컴포넌트의 상태(state) 또는 속성(props)에 의해 다시 계산되는 값
  4. JSX 생성

 

 

1️⃣ 상태 & 상태 업데이트 정의

 

(1) import로 내장 함수인 useState 불러옴 

import { useState } from 'react'

 

 

(2) 함수 컴포넌트 내에서 useState 선언

const [state, setState] = useState(state 초기값);
  • 상태 변화 함수는 setXXX으로 작명

 

 

2️⃣ 이벤트 핸들러 함수 내부에서 상태 업데이트

set업데이트 함수(수정할 state값);
  const handleCheck = (e) => {
    const nextCheckedValue = e.target.checked;

    // 선언된 리액트의 컴포넌트 상태 업데이트 트리거(렌더 요청)
    setChecked(nextCheckedValue);
  };

 

 

 

3️⃣ 파생된 상태

  • 컴포넌트의 상태(state) 또는 속성(props)에 의해 다시 계산되는 값
  const searchedUsersList = users.filter(
    (user) => user.name.includes(searchTerm) || user.email.includes(searchTerm)
  );

 

 

 

4️⃣ JSX 생성

  • return문 안에서 호출
{ state명 }
function App() {

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

  return (
    <>
    <h1>{state}</h1>
    </>
  )
}

 

 


 

예제 1) 체크박스 컴포넌트

import './TermAndConditions.css';
import React from 'react';

function TermAndConditions() {

  // 컴포넌트의 상태 선언
  const [checked, setChecked] = React.useState(false);

  // 이벤트 핸들러 함수(상테 업데이트 로직 포함)
  const handleCheck = (e) => {
    const nextCheckedValue = e.target.checked;

    // 상태 업데이트 트리거(렌더 요청)
    setChecked(nextCheckedValue);
  };

  // 파생된 상태(데이터)
  // - 리액트가 렌더 트리거를 받아 렌더하면 다시 계산
  const isDisabled = checked ? false : true;

  return (
    <form className="TermAndConditions">
      <h2>이용 약관</h2>
      <p>
        OOO 서비스를 이용함으로써 귀하는 본 약관에 동의하게 되므로 본 약관을
        숙지하는 것이 중요합니다. 본 약관 외에도 OOO은 개인정보처리방침을
        게시합니다.
      </p>
      <div>
        <input
          id="terms"
          name="terms"
          type="checkbox"
          checked={checked}
          onChange={handleCheck}
        />{' '}
        <label htmlFor="terms">이용 약관에 동의합니다.</label>
      </div>
      <button type="submit" disabled={isDisabled}>
        확인
      </button>
    </form>
  );
}

export default TermAndConditions;

 

 

 

 

예제 2) 텍스트 확장 컴포넌트

  1. `children` 속성(prop) 길이에 따라 확장 가능한 텍스트 렌더링
  2. `limit` 속성(기본 값: 255) 값보다 `children` 길이가 짧은 경우 텍스트만 표시
  3. `limit` 속성 값보다 `children` 길이가 긴 경우 텍스트 말줄임(...) 표시
  4. `limit` 속성 값보다 `children` 길이가 긴 경우 확장 or 축소 버튼 표시
  5. 확장 or 축소 버튼을 사용자가 클릭하면 텍스트 확장 또는 축소되어 표시
import { string, number } from 'prop-types';
import { useState } from 'react';
import './ExpandableText.css';

// 타입 검사
ExpandableText.propTypes = {
  children: string.isRequired,
  limit: number,
};


function ExpandableText({ children, limit = 255 }) {

  // 컴포넌트 상태를 객체로 선언
  const [state, setState] = useState({
    // 축소되어 있는 상태(확장 false)
    isExpand: false,
  });


  // 텍스트 길이(boolean)
  const isExpandable = children.length > limit;

  let renderText = children;

  if (isExpandable) {
    
    // limit보다 길이가 긴 텍스트는 limit 부터 '...' 붙이기
    renderText = children.slice(0, limit) + '...';
  }

  // 버튼 텍스트 변경
  const buttonLabel = state.isExpand ? '축소' : '확장';

  console.log(state.isExpand)

  // 이벤트 핸들러 함수
  const handleExpand = () => {

    // 확장되어 있는 상태면 true 변수 선언
    const nextExpandValue = !state.isExpand;

    // 컴포넌트 상태 업데이트
    // - isExpand(축소되어 있는 상태)를 true(확장 true)로 변경
    setState({
      isExpand: nextExpandValue,
    });
  };

  return (
    <div className="ExpandableText">
      <p>{state.isExpand ? children : renderText}</p>

      {/* 텍스트 길이가 limit보다 크면 */}
      {isExpandable && (
        <button type="button" onClick={handleExpand}>
          {buttonLabel}
        </button>
      )}
      
    </div>
  );
}

export default ExpandableText;

 

 

 

 

예제 3) 아바타 프로필 컴포넌트

  1. 데이터 분리
  2. 컴포넌트 상태로 정의(선언)
  3. 이벤트 핸들러 작성(기능 구현)
  4. 사용자 상호작용에 따라 화면 업데이트 (확인)
  5. 컴포넌트 테스트 (SKIP)
import { useState } from 'react';
import Avatar from '@/components/Avatar';
import { avatarsData } from '@/data/avatars';

function AvatarListPage() {

  // 컴포넌트 상태 선언
  const [list, setList] = useState(avatarsData);

  // 삭제 이벤트 핸들링 함수(상태 업데이트 로직)
  const handleDeleteItem = (deleteId) => () => {

    // 삭제된 프로필을 제외한 남은 프로필(변경 후의 상태 데이터)
    const nextList = list.filter((item) => item.id !== deleteId);
    // - deleteId: 현재 삭제 버튼을 누른 프로필 ID
    // - item.id: 데이터에 저장된 프로필 ID

    // 상태 업데이트 (렌더 트리거)
    setList(nextList);
  };

  
  // 프로필이 다 삭제되면
  if (list.length === 0) {
    return <p style={{ fontSize: 24 }}>화면에 표시할 아바타가 없습니다. 😳</p>;
  }


  return (
    <ul className="AvatarList">
      {list.map((item) => (
        <li key={item.id} style={{ textAlign: 'center' }}>
          <Avatar name={item.name} photo={item.photo} status={item.status} />
          
          <button
            type="button"
            onClick={handleDeleteItem(item.id)}
            style={{ marginBlockStart: 8 }}
          >
            삭제
          </button>
        </li>
      ))}
    </ul>
  );
}

export default AvatarListPage;

 

 

 

예제 4) 카운터 컴포넌트

  1. `count` 속성(prop, 기본 값: 1)을 전달받아 화면에 표시
  2. `step` 속성(기본 값: 1)을 전달받아 버튼 레이블에 표시
  3. `min` 속성(기본 값: 1) 보다 `count` 값이 크거나 같아야 함
  4. `max` 속성(기본 값: 1000) 보다 `count` 값이 작거나 같아야 함
  5. 사용자가 감소 버튼을 클릭하면 `count` 감소 (step 만큼)
  6. 사용자가 증가 버튼을 클릭하면 `count` 증가 (step 만큼)
  7. 사용자가 감소 버튼을 클릭했을 때 `count` 값이 `min` 보다 작거나 같을 경우 감소 버튼 비활성화
  8. 사용자가 증가 버튼을 클릭했을 때 `count` 값이 `max` 보다 크거나 같을 경우 증가 버튼 비활성화

* useState()에 콜백 함수를 넣어서 콜백 함수로 연산된 값을 state로 사용할 수 있다❗

import { useState } from 'react';
import { number } from 'prop-types';
import './Counter.css';

// 타입 검사
Counter.propTypes = {
  count: number,
  step: number,
  min: number,
  max: number,
};


function Counter({ count: initialCount = 1, step = 2, min = 1, max = 1000 }) {
  // count 변수에 initialCount 변수 할당, initialCount가 없으면 1 할당(구조 분해 할당)

  const [count, setCount] = useState(() => {
    // useState()에 콜백 함수를 넣어서 콜백 함수로 연산된 값을 state로 사용
    
    if (initialCount < min || initialCount > max) {
      throw new Error(`count 값이 min 보다 작거나, max보다 큽니다.`);
    }

    return initialCount;
    // return한 initialCount를 count(state) 값으로 설정함
  });


  const handleDecrease = () => {
    const nextCount = count - step;
    setCount(nextCount);
  };

  const handleIncrease = () => {
    const nextCount = count + step;
    setCount(nextCount);
  };

  
  // 버튼 비활성화 기준
  // - 증가 버튼 = 현재 값 <= 최솟값
  const isDisabledDecrease = count <= min;

  // - 감소 버튼 = 현재 값 >= 최댓값
  const isDisabledIncrease = count >= max;


  return (
    <div className="Counter">
      <button
        type="button"
        disabled={isDisabledDecrease}
        onClick={handleDecrease}
      >
        -{step}
      </button>

      <output>{count}</output>

      <button
        type="button"
        disabled={isDisabledIncrease}
        onClick={handleIncrease}
      >
        +{step}
      </button>
    </div>
  );
}

export default Counter;


<!-- Playground.jsx -->
<Counter count={3} min={2} max={6} />

 


 

class 컴포넌트

  • class 컴포넌트에서는 setState()로 state를 변경해주면, class 내부의 render 함수를 따로 지정하지 않아도(인수로 넘겨주지 않아도) 렌더를 실행해준다.
  • 레거시 방법이므로 참고만 하기
import { Component } from 'react';
import './TermAndConditions.css';

class TermAndConditions extends Component {
  constructor(props) {
    // 컴포넌트에 전달된 속성(props)는 읽기 전용
    super(props);

    // 컴포넌트의 데이터 기억(메모리)
    // 컴포넌트 속성(props)와 달리 상태 값 수정 가능
    this.state = {
      agreement: false,
    };
  }

  render() {
    const { agreement } = this.state; // { agreement: boolean }

    // 컴포넌트 속성 또는 상태로부터 파생된 상태 (derived state: from state or props)
    const isDisabled = agreement ? false : true;

    return (
      <form className="TermAndConditions" onSubmit={this.handleSubmit}>
        <h2>이용 약관</h2>
        <p>
          OOO 서비스를 이용함으로써 귀하는 본 약관에 동의하게 되므로 본 약관을
          숙지하는 것이 중요합니다. 본 약관 외에도 OOO은 개인정보처리방침을
          게시합니다.
        </p>
        <div>
          <input
            id="terms"
            name="terms"
            type="checkbox"
            checked={agreement}
            onChange={this.handleCheck}
          />{' '}
          <label htmlFor="terms">이용 약관에 동의합니다.</label>
        </div>
        <button type="submit" disabled={isDisabled}>
          확인
        </button>
      </form>
    );
  }

  handleSubmit = (e) => {
    e.preventDefault();
  };


  // 사용자에 의한 액션에 의한 컴포넌트의 상태 업데이트
  handleCheck = (e) => {
    const { checked: nextAgreement } = e.target;

    // 리액트에게 렌더 트리거(요청)
    // - 클래스 컴포넌트에서의 상태 업데이트 API: this.setState(nextState)
    this.setState({ agreement: nextAgreement });
  };
}

export default TermAndConditions;

 

 


 

📢 핵심 정리

  • 리액트는 "선언적"이다.
  • 리액트의 컴포넌트는 내부에 데이터를 기억할 수 있다. (= "상태(state)")
  • 리액트에서 다루는 데이터는 "속성(props)"과 "상태(states)"이다.
  • 리액트에서 다루는 데이터 중
    - 읽기/쓰기가 가능한 데이터는 "상태"이다.
    - 읽기만 가능한 데이터는 "속성"이다.
  • 리액트에 의해 "선언된 상태"는 업데이트가 가능하다.
  • 업데이트 된 선언된 상태를 감지해 리액트는 JSX를 반환하는 함수(또는 render 메서드)를 다시 실행한다.
  • JSX를 반환하는 함수(또는 render 메서드)를 다시 실행되면, 이를 ReactDOM이 실제 DOM에 반영(commit)한다.
  • 반영된 DOM을 브라우저는 다시 페인팅(Painting) 한다.

 


 

 

State: 컴포넌트의 기억 저장소 – React

The library for web and native user interfaces

ko.react.dev

 

 

useState – React

The library for web and native user interfaces

ko.react.dev