본문 바로가기
개발 학습/프론트엔드

개발간에 썼던 React use Hook - 1

by StelthPark 2024. 8. 22.

1. useCallback

부모컴포넌트에서 자식컴포넌트로 onClick 함수를 전달해주는 상황일 때 2가지 상황에 대해 생각해보자

const Root = () =>{
  return (
    <>
    <Number onClick={()=> console.log('click !')}>
    <Number onClick={()=> console.log('click !')}>
      <Number onClick={()=> console.log('click !')}>  
        </>
)
}

const Number = ({onClick}) =>{
  return(
    <button onClick={onClick}>버튼 </button>
)

 

해당코드는 Number 컴포넌트로 onClick 함수를  직접 생성해서 전달해주기 때문에 렌더링이 발생할 때 마다 새로 함수다 생성된다.

 

const Root = () =>{
    const onClick = () =>{
      console.log('click !')
    }
    return (
      <Number onClick={onClick}/>
      <Number onClick={onClick}/>
      <Number onClick={onClick}/>
	)
}

const Number = ({onClick}) =>{
    return(
      <button onClick={onClick}>버튼 </button>
	)
}

해당 코드는 Root 부모 컴포넌트 내부에서 함수를 선언하고 해당 함수를 자식 컴포넌트로 내려주기 때문에 Number함수가 리렌더 될 때만 내부함수도 리렌더 된다.

 

여기서 useCallback을 사용한다면,

const Root = () =>{
  
	const [ clicked , setClicked ] = useState(false);
  
    const onClick =useCallback(() => {
		setClicked(true)	
    },[])
    
    return (
      <Number onClick={onClick}/>
      <Number onClick={onClick}/>
      <Number onClick={onClick}/>
	)
}

const Number = ({onClick}) =>{
    return(
      <button onClick={onClick}>버튼 </button>
	)
}

 

넘겨주어야할 함수 onClick을 useCallback으로 감싸주었다. useCallback의 두번째 인자에 빈배열을 줌으로써 onClick 함수는 최초 한번만 생성되고 이후에는 같은 함수 인스턴스를 사용하게 된다. 이로인해 onClick함수가 변경되지 않는 한 Number 컴포넌트도 당연 Props 가 변경되지 않았기에 리렌더되지 않는다.

 

이유는 자바스크립트에서 함수는 객체이기때문에 객체는 메모리에 값을 저장하는게 아니라 값의 주소를 저장하기 때문에 같은 값이라도 일치연산자로 비교해보면 false가 출력되며 props로 함수를 전달하더라도 같은 함수일지라도 다른 주소를 가진 함수가 전달되어 변경된 것으로 감지되어 리렌더되는 것이다.

 

이를 useCallback이 react 함수를 저장하고 두번째 매개변수 dependencies가 변경되지 않는 한 함수를 재성성하지 않아 자식컴포넌트들은 전달받은 props가 변경되지 않았다고 생각하게 된다.

 

이렇게 useCallback은 이러한 상황에서 함수를 캐싱하여 같은 함수 인스턴스를 사용하도록 도와주며, 렌더링 성능을 최적화하는데 도움을 준다.

 

그렇다면 useCallack, 무조건 많이 쓰면 좋을까?

useCallback을 모든함수에 적용하면 코드가 복잡해지고 이를 모든함수를 메모이제이션 하려는 시도때문에 불필요한 최적화가 발생한다.

물론 함수 인스턴스를 메모리에 유지해야하므로 메모리 사용량이 증가하게 되며 특별한 경우가 아니라면 캐시된 함수를 계속해서 보관하게 된다.

 

2. useMemo

useMemo는 메모이제이션된 값을 반환하는 리액트 훅이다. 앞서 말한 '메모이제이션 피보나치 함수'와 같이 직전에 연산된 값이 있다면, 다시 연산을 하지 않고, 해당 값을 반환한다.

 

const value = useMemo(() => {
	return calculate();
}. [item])

 

종속값이 든 두번째 인자값에 따라 다시 calculate 함수를 실행하게 된다. 만약 빈 배열을 넣는다면 useEffect와 마찬가지로 마운트 될 때에만 값을 계산하고 그 이후론 계속 memoization된 값을 꺼내와 사용한다.

 

caculate 함수가 어려운 연산을 해야하는 경우 useMemo를 쓰지않으면 caculate함수로 파라미터가 전달되는 상황에서 해당 파라미터가 변경될때마다 계속해서 연산을 해주어야한다. 이때 useMemo의 두번째 인자로 해당 파라미터를 넣어 변경될때만 콜백함수를 실행하게 한다면 불필요한 연산을 막을 수 있게 된다.

 

3. useRef

useRef 는 함수 컴포넌트에서 DOM 요소나 다른 값들을 참조할 수 있게 해주는 기능을 제공한다 주로 DOM 요소에 직접 접근해야 하는 경우나 컴포넌트 간의 통신에 사용된다.

 

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

const CounterComponent = () => {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  // 이전 상태를 useRef를 사용하여 저장
  useEffect(() => {
    prevCountRef.current = count;
  }, [count]);

  const handleIncrement = () => {
    setCount(prevCount => prevCount + 1);
  };

  const handleDecrement = () => {
    setCount(prevCount => prevCount - 1);
  };

  return (
    <div>
      <h2>Count: {count}</h2>
      <h3>Previous Count: {prevCountRef.current}</h3>
      <button onClick={handleIncrement}>증가</button>
      <button onClick={handleDecrement}>감소</button>
    </div>
  );
};

export default CounterComponent;

 

count값이 변경될 때 이전 count 값을 저장 할 수 있다. 이러한 방식으로 useRef를 사용하면 함수 컴포넌트 내에서 이전 상태를 유지할 수 있으며, 이를 통해 이전 상태를 활용하는 다양한 기능을 구현할 수 있다.

 

useRef의 주요 특징이다.

1. 렌더링과 관련 없는 값을 저장 하는데, 저장된 값은 컴포넌트 렌더링과 관계없이 유지되므로 렌더링과 관련없는 값만 저장하도록 하자.

2. DOM요소에 직접 접근하므로 렌더링이 된 후접근을 해야한다. 그래서 useEffect로 렌더링이 완료 되면 ref로 접근을 하도록 하자.

3. useRef는 값이 변경될 때 재 렌더링 되지 않는다.

 

 

4. 가장 많이 썼던 useStae, useEffect

 

💥 useState 주의점

useState는 상태를 관리하고, 상태가 변경되면 해당 컴포넌트가 다시 렌더링되기 때문에, 상태를 렌더링에 영향을 줄 수 있습니다.
 

  1. 무한 루프에 빠지지 않도록 주의: useState를 사용하여 상태를 업데이트할 때, 상태 변경이 렌더링을 유발하고, 다시 렌더링은 상태를 다시 확인하여 업데이트를 유발할 수 있습니다. 이런 과정이 끊임없이 반복되면 무한 루프에 빠질 수 있습니다. 이를 방지하기 위해 useEffect나 useCallback 등을 사용하여 상태 변경에 따라 불필요한 렌더링이 발생하지 않도록 조치해야 합니다.
  2. 렌더링 비용 최적화: 상태를 렌더링할 때는 해당 상태가 렌더링에 어떤 영향을 미치는지 고려해야 합니다. 불필요한 상태 변경을 줄이고, 상태 업데이트를 최적화하여 성능을 향상시키는 것이 중요합니다. 또한, useState를 사용하여 렌더링할 때는 상태 변경이 해당 컴포넌트뿐만 아니라 하위 컴포넌트에도 영향을 미치는지 고려해야 합니다.
  3. 상태 변경 시 렌더링 횟수 제어: 상태 변경에 따라 렌더링이 여러 번 발생하는 것을 방지하기 위해, 상태 변경이 발생하는 곳에서 최적화를 고려해야 합니다. 예를 들어, useState를 사용하여 상태를 업데이트하는 함수를 useCallback으로 감싸서 불필요한 렌더링을 방지할 수 있습니다.


💥 useEffect 주의점

  1. 의존성 배열의 사용: useEffect에서 의존성 배열을 지정하여 특정 상태나 프로퍼티가 변경될 때만 부수 효과를 실행할 수 있습니다. 이를 통해 불필요한 부수 효과 실행을 방지할 수 있습니다.
  2. 정리 함수 반환: useEffect 안에서 부수 효과를 정의할 때는 옵셔널로 정리(clean-up) 함수를 반환할 수 있습니다. 이 함수는 컴포넌트가 언마운트되거나 업데이트되기 직전에 실행되어 리소스를 정리하거나 구독을 해제하는 등의 작업을 수행합니다.
  3. 비동기 작업 처리: useEffect 안에서 비동기 작업을 처리할 때는 콜백 함수를 async 함수로 정의하거나, 콜백 함수 내에서 async 함수를 호출하는 방식으로 처리할 수 있습니다. 단, useEffect 자체는 async 함수를 지원하지 않으므로 이에 유의해야 합니다.
  4. 의존성 배열의 사용에 대한 경고: 의존성 배열을 사용할 때 모든 의존성을 배열에 포함시키는 것이 중요합니다. 의존성을 누락하면 부수 효과가 예상대로 동작하지 않을 수 있습니다.

 

댓글