빙응의 공부 블로그

[React]리액트 -State and Lifecycle, hooks 본문

React

[React]리액트 -State and Lifecycle, hooks

빙응이 2024. 5. 9. 18:00

📝State란 무엇일까?

리액트의 핵심인 State는 한글로 상태를 뜻한다.
리액트에서의 상태는 리액트 Component의 상태를 의미한다.
쉽게 말하면 리액트의 변경 가능한 데이터를 State라고 한다. 

🚩 중요점

  • State는 렌더링이나 데이터 흐름에 사용되는 값만 포함 시켜야 한다.
    • 스테이트를 변경할 경우 재렌더링 되기 때문에 성능 저하가 발생할 수 있기 때문이다. 
  • State는 JavaScript의 객체이다. 

 

위 사진은 클래스 컴포넌트를 나타내며 constructor는 생성자이다.

  • 붉은 네모칸이 바로 State를 정의하는 부분이다.

🚩 State는 직접 수정하면 안된다.

이 말의 의미가 무엇이냐면 자바로 관점에서 class의 객체를 직접 수정하면 안되듯이 리액트에서 getter, setter를 사용해야 한다.

// 직접 수정, 잘못된 사용방법
this.state = {
	name : 'jsad'
};
// 함수를 통한 수정, 정상적인 사용방법
this.setState({
	name: 'jsda'
});

 

 

📝Lifecycle 

클래스 컴포넌트가 생성되는 시점과 사라지는 시점이 정해져있다.

단! 최근에는 클래스 컴포넌트를 잘 사용하지 않으므로 이것이 있다 정도만 기억해두자

한마디로 정의하자면
Component가 계속 존재하는 것이 아니라, 시간의 흐름에 따라 생성되고 업데이트 되다가 사라진다.

 

📝 hooks

앞 포스팅에서 리액트 컴포넌트에 대해 배울때 2가지 컴포넌트가 있다고 하였다.

  • Function Conponent
    • State 사용 불가
    • Lifecycle에 따른 기능 구현 불가
  • Class Component
    • 생성자에서 state를 정의
    • setState() 함수를 통해 state 업데이트
    • Lifecycle 모듈 제공

 

이렇게 함수 컴포넌트에 클래스 컴포넌트와 동일한 기능을 사용하게 해주는 것이 바로 hook이다.

function MyComponent(props) {


     //state 관련 hook 함수


     //Lifecycle 관련 hook 함수


     //최적화 관련 hook 함수



     return (
         <div>
              안녕하세요.
         </div>
     )
}

 

 

🚩 useState

말 그대로 State를 사용하기 위한 훅이다.

import React, { useState } from "react";


function Counter(props) {
     var count = 0;
     
     return (
          <div>
               <p>총 {count}번 클릭했습니다.</p>
               <button onClick={() => count++}>
                    클릭
               </button>
          </div>
    );
}

위 함수 컴포넌트는 버튼을 클릭할 때마다 숫자가 올라가는 로직이지만

  • count 변수는 State가 아니기에 재렌더링이 일어나지 않는다.
import React, { useState } from "react";


function Counter(props) {
     const [count, setCount] = useState(0);
     
     return (
          <div>
               <p>총 {count}번 클릭했습니다.</p>
               <button onClick={() => setCount(count + 1)}>
                    클릭
               </button>
          </div>
    );
}
  • 위 처럼 useState를 사용하면 재렌더링을 구현할 수 있다. 
  • 또한 변수마다 set 함수가 따로 존재한다.!

 

🚩 useEffect

useEffect는 Side Effect를 수행하기 위한 Hook이다.

Side effect = 효과, 영향
서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 작업
다른 컴포넌트에 영향을 미칠 수 있어 렌더링이 끝난 이후에 시작해야하는 작업이다. 
  • useEffect는 리액트 함수 컴포넌트에서 Side Effect를 실행할 수 있게 도와준다.
    • 이것은 클래스 컴포넌트의 생명주기 함수를 하나의 기능으로 통합하여 재공한다.
import React, { useState } from "react";


function Counter(props) {
     const [count, setCount] = useState(0);
     
     // conponentDidMount, componentDidUpdate와 비슷하게 작동한다.
     // 처음 시작, 업데이트 될 때마다 불러오는 함수 
     useEffect(() => {
     	document.title = 'You clicked ${count} times';
        
        //컴포넌트가 unmount 될때 호출된다
        return () => {
        	document.title = '컴포넌트 종료' ;
        }
     });
     
     return (
          <div>
               <p>총 {count}번 클릭했습니다.</p>
               <button onClick={() => setCount(count + 1)}>
                    클릭
               </button>
          </div>
    );
}

 

위의 코드는 기존 코드에 useEffect를 추가한 것이다. 

 

     // conponentDidMount, componentDidUpdate와 비슷하게 작동한다.
     // 처음 시작, 업데이트 될 때마다 불러오는 함수 
     useEffect(() => {
     	document.title = 'You clicked ${count} times';
        //컴포넌트가 unmount 될때 호출된다
        return () => {
        	document.title = '컴포넌트 종료' ;
        }
     });

해당 명령어는 클래스 컴포넌트의 conponentDidMount, componentDidUpdate와 비슷하게 동작하며

useEffect 안에 return은 함수 생명주기가 끝나기 전에 호출된다.! 

 

📝 성능 최적화 훅

🚩 useMemo

useMemo는 Memoized value를 리턴하는 훅이다.

Memoization : 최적화를 하기 위해서 사용하는 개념
비용이 많은 함수의 결과를 저장하였다가 같은 입력 값이 들어오면 전에 저장한 것을 리턴하는 개념이다.

 

const memoizedValue = useMemo(
     () => {
         return computeExpensiveValue(의존성 변수1, 의존성 변수2);
     };
     [의존성 변수, 의존성 변수2]
);
import React, { useMemo } from 'react';

function ExpensiveComponent({ a, b }) {
  // a와 b 값이 변경될 때만 add 함수가 다시 실행되고 결과를 캐싱
  const sum = useMemo(() => {
    console.log('Calculating sum...');
    return a + b;
  }, [a, b]);

  return <p>Sum: {sum}</p>;
}

useMemo는 파라미터로 memoizedValue를 생성하는 create 함수와 의존성 배열을 받는다.

  • Memoization의 개념처럼 의존성 배열에 들어있는 변수가 변했을 경우에만 새로 create함수를 호출하여 결과값을 반환하며 그렇지 않을 경우 그대로 반환한다.
  • useMemo에 들어있는 함수는 렌더링 중에 실행되는 함수이므로 렌더링 중 실행할 수 없는 함수는 넣으면 안된다.

🚩 useCallback

useMemo와 유사하지만 값이 아닌 함수를 반환한다.

const memoizedCallback = useCallback(
     () => {
         computeExpensiveValue(의존성 변수1, 의존성 변수2);
     },
     [의존성 변수, 의존성 변수2]
);

컴포넌트가 렌더링할 때마다 매번 함수를 새로 정의하는 것이 아니라 의존성 배열의 값이 바뀐 경우에만 함수를 새로 정의해서 리턴한다. 

import React, { useState, useCallback } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  // handleClick 함수를 useCallback으로 감싸서 재사용 가능한 콜백 함수로 만듦
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]); // count가 변경될 때만 함수가 새로 생성됨

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

 

 

최적화 정리
  • 콜백(callback): 특정 조건이나 상황에 따라 실행되는 함수를 다른 함수의 인자로 전달하는 것. 외부 조건에 의해 실행 여부가 결정
  • 메모ization(memoization): 이전에 계산한 결과를 저장해두고, 동일한 입력이 들어오면 저장된 결과를 반환하여 함수의 재실행을 피하는 최적화 기법이다.. 함수의 입력 값에 따라 결과를 캐싱하고 재활용

🚩 useRef 

useRef는 Reference를 사용하기 위한 훅이다.

Reference란 특정 컴포넌트에 접근할 수 있는 객체를 의미한다.
useRef은 이 레퍼런스 객체를 반환한다.
Current
  • 레퍼런스 객체에는 current라는 속성이 있는데 이것은 현재 참조하고 있는 엘리먼트를 의미한다.
사용법
const refContatiner = useRef(초깃값);
  • 파라미터로 초기값을 넣으면 해당 초기값으로 초기화된 레퍼런스 객체를 반환한다.
  • 이렇게 반환된 레퍼런스 객체는 컴포넌트의 라이프타임 전체에 걸쳐서 유지된다.
    • 즉 언마운트 전까지 유지
import { useRef } from 'react';

function MyComponent() {
  const inputRef1 = useRef(null);
  const inputRef2 = useRef(null);

  const focusInput1 = () => {
    inputRef1.current.focus();
  };

  const focusInput2 = () => {
    inputRef2.current.focus();
  };

  return (
    <div>
      <input ref={inputRef1} type="text" />
      <button onClick={focusInput1}>Focus Input 1</button>
      
      <input ref={inputRef2} type="text" />
      <button onClick={focusInput2}>Focus Input 2</button>
    </div>
  );
}

쉽게 말하면

컴포넌트로 여러 개의 엘리먼트 객체를 만들어도 useRef를 통해 해당 객체의 DOM을 직접 접근이 가능해진다.

 

 

📝 Hook의 규칙과 커스텀 Hook

 

🚩 규칙

1
Hook은 무조건 최상위 레벨에서만 호출되어야 한다.
  • 즉, 반복문, 조건문 또는 중첩된 함수들 안에서 Hook을 호출하면 안된다.
  • 그렇기에 Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출되어야 한다
2
리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.

🚩 커스텀 훅

예제를 통해 커스텀 훅을 만들어보자

import React, { useState, useEffect } from "react";

function UserListItem(props) {
    // 상태 변수(isOnline)와 해당 상태를 업데이트할 함수(setIsOnline)를 선언합니다.
    const [isOnline, setIsOnline] = useState(null);

    // 컴포넌트가 마운트될 때와 언마운트될 때 실행되는 부수 효과 로직을 정의합니다.
    useEffect(() => {
        // 사용자 상태가 변경될 때 호출될 콜백 함수를 정의합니다.
        function handleStatusChange(status) {
            setIsOnline(status.isOnline); // 사용자 온라인 상태를 업데이트합니다.
        }

        // ServerAPI를 통해 해당 사용자의 상태를 구독합니다.
        ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);

        // 컴포넌트가 언마운트될 때, 구독을 해제하기 위해 정리(clean-up) 함수를 반환합니다.
        return () => {
            ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
        };
    }, []); // 의존성 배열이 비어있으므로, 컴포넌트가 처음 마운트될 때 한 번만 실행됩니다.

    // 사용자 정보를 나타내는 리스트 아이템을 렌더링합니다.
    return (
        <li style={{ color: isOnline ? 'green' : 'black' }}>
            {props.user.name}
        </li>
    );
}

export default UserListItem;

이 예제 컴포넌트는 사용자의 상태가 온라인인지 아닌지를 검사하는 컴포넌트이다.

 

이 컴포넌트에서 재사용을 위해 커스텀 훅으로 만들어보자

import React, { useState, useEffect } from "react";

// 커스텀 훅 useUserStatus 정의
function useUserStatus(userId) {
    const [isOnline, setIsOnline] = useState(null);

    useEffect(() => {
        // 사용자 상태가 변경될 때 호출될 콜백 함수
        function handleStatusChange(status) {
            setIsOnline(status.isOnline); // 사용자 온라인 상태 업데이트
        }

        // 사용자 상태를 구독
        ServerAPI.subscribeUserStatus(userId, handleStatusChange);

        // 컴포넌트가 언마운트될 때 구독 해제
        return () => {
            ServerAPI.unsubscribeUserStatus(userId, handleStatusChange);
        };
    }, [userId]); // userId가 변경될 때마다 useEffect 다시 실행

    return isOnline; // 상태 값 반환
}

// UserListItem 컴포넌트에서 useUserStatus 훅을 사용
function UserListItem(props) {
    const isOnline = useUserStatus(props.user.id);

    return (
        <li style={{ color: isOnline ? 'green' : 'black' }}>
            {props.user.name}
        </li>
    );
}

컴포넌트로부터 중복된 로직을 제거한다.

  • 여기서 주의점은 커스텀 훅은 무조건적으로 use로 시작해야한다.