[#60] React - Hook + 성능 최적화 정리

2025. 4. 23. 15:28·LG 유플러스 유레카 SW/React

React Hook

useState

https://nueos.tistory.com/204#useState-1

 

[#50] React - useState

useState- 컴포넌트에 상태를 추가하는 Hook- 화면에 보여주는 데이터를 저장하고 변경할 수 있게 해주는 기능기본 사용법const [상태, 상태변경함수] = useState(초기값);const [state, setState] = useState(초기

nueos.tistory.com

 

useEffect

컴포넌트의 렌더링 외에 발생하는 부수 효과(side effects)를 처리하기 위한 훅

더보기

부수 효과

  • 데이터 가져오기 (API 호출)
  • 타이머 설정 (setTimeout)
  • 이벤트 리스너 등록
  • 외부 시스템과 연동
useEffect(() => {
  // 마운트될 때 실행할 코드 

  return () => {
  // 언마운드 될 때 실행할 코드 -  정리(cleanup) 함수 - 선택적
  };
}, [의존성1, 의존성2, ...]); //의존성이 업데이트 되면 useEffect() 재 실행

실행 시점

  • 마운트 시: 컴포넌트가 처음 화면에 나타날 때
  • 업데이트 시: 의존성 배열에 있는 값이 변경될 때 (의존성 배열이 있는 경우)
  • 언마운트 시: 컴포넌트가 화면에서 사라질 때 (정리 함수 실행)

의존성 배열

  • 빈 배열 []: 컴포넌트가 마운트될 때 한 번만 실행, 언마운트 시 정리 함수 실행
  • 의존성 포함 [value1, value2]: 해당 값들이 변경될 때마다 실행
  • 배열 생략: 모든 렌더링 후 실행 (권장 X)

정리(Cleanup) 함수

  • 이전 효과를 정리하기 위한 함수
  • 메모리 누수 방지
  • 이벤트 리스너, 타이머 등을 해제할 때 필요
import { useState, useEffect } from 'react';

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

  useEffect(() => {
    // 타이머 설정
    const timerId = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    // 정리 함수: 타이머 제거
    return () => {
      clearInterval(timerId);
    };
  }, []); // 마운트 시 한 번만 실행

  return <h1>카운트: {count}</h1>;
}

 

useRef

리렌더링을 발생시키지 않으면서 값을 저장할 수 있는 훅

👉🏻 컴포넌트 안에 있는 '비밀 상자'

const ref = useRef(초기값);
  • 일반 변수와 달리 컴포넌트가 다시 렌더링되어도 ref에 저장된 값은 그대로 유지됨
  • ref 값을 바꿔도 화면이 업데이트 되지 않음 (useState와 가장 큰 차이점)
  • 항상 ref.current 속성을 통해 값에 접근하고 수정

🧩 활용 사례

1️⃣ DOM 요소에 직접 접근

input, canvas, video 등 HTML 요소에 직접 접근 가능

const InputFocus = () => {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus(); // input에 포커스
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>포커스 이동</button>
    </>
  );
};

2️⃣ 이전 값 기억하기

const PreviousValue = ({ count }) => {
  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count;
  }, [count]);

  return (
    <div>
      현재 값: {count}, 이전 값: {prevCountRef.current}
    </div>
  );
};

3️⃣ 스크롤 위치 제어

function ScrollComponent() {
  const scrollAreaRef = useRef(null);
  
  const scrollToTop = () => {
    scrollAreaRef.current.scrollTop = 0;
  };
  
  const scrollToBottom = () => {
    const { current } = scrollAreaRef;
    current.scrollTop = current.scrollHeight;
  };
  
  return (
    <>
      <button onClick={scrollToTop}>Scroll to Top</button>
      <button onClick={scrollToBottom}>Scroll to Bottom</button>
      
      <div 
        ref={scrollAreaRef}
        style={{ height: '300px', overflow: 'auto' }}
      >
        {/* Scrollable content */}
        {Array.from({ length: 50 }, (_, i) => (
          <p key={i}>Item {i + 1}</p>
        ))}
      </div>
    </>
  );
}

⚠️ 주의 사항

1. 렌더링 중에 ref.current 읽거나 쓰지 않기

  • DOM 요소나 인스턴스를 가리키는데, 컴포넌트가 렌더링 중일 때는 ref가 아직 연결되지 않았을 수 있음
  • 이벤트 핸들러나 useEffect 내부에서 사용
// ❌ 잘못된 예
const height = scrollRef.current.scrollHeight  // 아직 ref 연결 전일 수 있음

// ✅ 좋은 예
useEffect(() => {
  console.log(scrollRef.current.scrollHeight) // 마운트 이후 안전하게 접근
}, [])

2. UI에 표시할 값은 state 사용하기

// ❌ 잘못된 예
const countRef = useRef(0)
// UI에 countRef.current를 직접 쓰면 업데이트 안됨

// ✅ 좋은 예
const [count, setCount] = useState(0)
// 상태가 바뀌면 자동으로 재렌더링됨

3. 초기화 비용이 큰 값은 조건부로 초기화하기

const playerRef = useRef(null);

// 최초 한 번만 초기화
if (playerRef.current === null) {
  playerRef.current = new VideoPlayer();
}

 

useNavigate

페이지 이동을 하기 위해 사용하는 훅

👉🏻 링크를 클릭하지 않고도 버튼 클릭, 폼 제출, 조건 만족 시 JS 코드 내부에서 이동이 필요할 때 유용

import { useNavigate } from 'react-router-dom';

function Example() {
  const navigate = useNavigate();

  const goToHome = () => {
    navigate('/');
  };

  return <button onClick={goToHome}>홈으로 이동</button>;
}

📍 주요 기능 및 옵션

1️⃣ 기본 이동

navigate('/about');

2️⃣ 파라미터 포함한 이동

navigate(`/detail/${id}`);

3️⃣ 쿼리 파라미터 포함 이동

navigate(`/search?q=apple`);

4️⃣ replace 옵션(뒤로 가기 방지)

navigate('/login', { replace: true });

5️⃣ 이전 페이지로 이동

navigate(-1); // history.back()

 

useLoaction

현재 브라우저의 URL 정보를 가져오는 데 사용되는 훅

👉🏻 컴포넌트 내에서 현재 페이지의 경로, 쿼리 매개 변수, 해시 등 URL 관련 정보에 접근할 수 있게 해줌

📍 반환하는 객체의 주요 속성

  1. pathname: 현재 URL의 경로 부분 (예: '/about')
  2. search: URL의 쿼리 문자열 부분 (예: '?id=123')
  3. hash: URL의 해시 부분 (예: '#section1')
  4. state: 페이지 이동 시 전달된 상태 객체
  5. key: 각 위치에 대한 고유 식별자
import { useLocation } from 'react-router-dom';

function MyComponent() {
  const location = useLocation();

  console.log(location.pathname);// 현재 경로
  console.log(location.search);// 쿼리 문자열
  console.log(location.hash);// URL 해시
  console.log(location.state);// 전달된 상태 객체

  return (
    <div>Current path: {location.pathname}</div>
  );
}

🧩 활용 사례

1️⃣ 페이지 이동 시, 상태 전달 및 접근

// 페이지 A
import { useNavigate } from 'react-router-dom';

function PageA() {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate('/page-b', {
      state: {
        from: 'pageA',
        data: { id: 123, name: 'Example' }
      }
    });
  };

  return <button onClick={handleClick}>페이지 B로 이동</button>;
}

// 페이지 B
import { useLocation } from 'react-router-dom';

function PageB() {
  const location = useLocation();
  const { from, data } = location.state || {};

  return (
    <div>
      <p>이전 페이지: {from}</p>
      <p>전달된 데이터: {data ? JSON.stringify(data) : '없음'}</p>
    </div>
  );
}

2️⃣ 현재 경로에 따른 UI 조건부 렌더링

import { useLocation } from 'react-router-dom';

function Navigation() {
  const location = useLocation();

  return (
    <nav>
      <ul>
        <li className={location.pathname === '/' ? 'active' : ''}>
          <a href="/">Home</a>
        </li>
        <li className={location.pathname === '/about' ? 'active' : ''}>
          <a href="/about">About</a>
        </li>
        <li className={location.pathname.startsWith('/products') ? 'active' : ''}>
          <a href="/products">Products</a>
        </li>
      </ul>
    </nav>
  );
}

 

useMemo

계산 비용이 높은 값을 기억(memoization)해두고, 의존성(deps)이 바뀔 때만 다시 계산하도록 도와주는 훅

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

🧩 활용 사례

1️⃣ 무거운 연산 메모이제이션

data가 변경되지 않는 한, heavyCalulation은 다시 실행되지 않음

const expensiveResult = useMemo(() => {
  console.log("계산 중...");
  return heavyCalculation(data);
}, [data]);

2️⃣ 컴포넌트 성능 최적화

리스트가 바뀔 때만 정렬 실행

const sortedList = useMemo(() => {
  return list.slice().sort((a, b) => a - b);
}, [list]);

⚠️ 주의 사항

  • useMemo는 무조건 성능 향상이 되는 것은 아님
  • 불필요하게 쓰면 메모리만 낭비할 수 있음
  • 캐시 비용보다 연산 비용이 클 때만 사용

 

useCallback

함수를 기억(memoization)해두는 훅

👉🏻 의존성 배열이 바뀌지 않는 한, 같은 함수 참조를 유지함

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

🧩 활용 사례

1️⃣ 자식 컴포넌트에 함수 props로 전달

handleClick이 같은 함수 참조를 유지해서 불필요한 자식 렌더링을 막을 수 있음

const handleClick = useCallback(() => {
  console.log("clicked!");
}, []);

// 자식 컴포넌트가 React.memo일 때 유용
<ChildComponent onClick={handleClick} />

2️⃣ API 호출 함수 최적화

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

// API 호출을 위한 함수
const fetchData = async (id: number) => {
  const response = await fetch(`https://api.example.com/data/${id}`);
  return response.json();
};

export default function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  // useCallback을 사용하여 fetchData 함수 메모이제이션
  const getData = useCallback(async (id: number) => {
    setLoading(true);
    setError(null);
    try {
      const result = await fetchData(id);
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, []); // 의존성 배열이 빈 배열로 되어 있어, 최초 한 번만 함수가 생성됨

  useEffect(() => {
    // 컴포넌트가 마운트될 때 데이터 호출
    getData(1);  // 예시로 1번 데이터 요청
  }, [getData]);  // getData가 변경될 때마다 호출

  return (
    <div>
      <h1>Data Fetching Example</h1>
      {loading && <p>Loading...</p>}
      {error && <p>Error: {error}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

⚠️ 주의 사항

  • 모든 함수에 무조건 쓰면 오히려 역효과(메모이제이션 비용도 존재함)
  • React.memo와 세트처럼 쓰는 경우가 많음
더보기

React.memo

props가 변경되지 않으면 해당 컴포넌트를 다시 렌더링하지 않고, 이전 렌더링 결과를 재사용할 수 있게 해주는 고차 컴포넌트

👉🏻 불필요한 렌더링 최적화

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

// React.memo로 감싸서 props가 바뀔 때만 렌더링되게
const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click Me</button>;
});

const Parent = () => {
  const [count, setCount] = useState(0);

  // useCallback을 쓰지 않으면 Parent가 리렌더링 될 때마다 onClick 함수도 새로 생성됨
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>Increment</button>
      <Child onClick={handleClick} />
    </div>
  );
};

export default Parent;

 

 

 

useSearchParams

URL의 쿼리 파라미터를 쉽게 읽고 수정할 수 있게 해주는 훅

const [searchParams, setSearchParams] = useSearchParams();
  • searchParams: URLSearchParams 인스턴스로, 현재 URL의 쿼리 파라미터에 접근할 수 있음
  • setSearchParams: 쿼리 파라미터를 업데이트하는 함수

📍 특징

  1. 읽기 기능: searchParams.get('paramName')을 사용하여 특정 쿼리 파라미터 값을 읽을 수 있음
  2. 쓰기 기능: setSearchParams({ paramName: 'value' })를 사용하여 URL의 쿼리 파라미터를 업데이트할 수 있음
  3. URL 동기화: 쿼리 파라미터를 변경하면 자동으로 브라우저의 URL이 업데이트되고, 해당 상태가 브라우저의 히스토리에 추가됨
  4. 복수 값 지원: 같은 이름의 여러 파라미터 값을 searchParams.getAll('paramName')로 가져올 수 있음

🧩 활용 사례

1️⃣ 필터링 기능

필터링을 URL 쿼리 파라미터로 유지하여 사용자가 필터링된 상태를 북마크하거나 공유할 수 있게 하는 경우

import { useSearchParams } from 'react-router-dom';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get('category');

  const handleFilterChange = (newCategory) => {
    setSearchParams({ category: newCategory });
  };

  return (
    <>
      <button onClick={() => handleFilterChange('shoes')}>Shoes</button>
      <button onClick={() => handleFilterChange('clothes')}>Clothes</button>
      <p>Selected Category: {category}</p>
    </>
  );
}

2️⃣ 페이지네이션

페이지 번호와 페이지당 항목 수를 URL에 유지하는 경우

function PaginatedList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const page = parseInt(searchParams.get('page') || '1', 10);
  const pageSize = parseInt(searchParams.get('size') || '10', 10);

  const handlePageChange = (newPage) => {
    setSearchParams({ page: newPage.toString(), size: pageSize.toString() });
  };

  return (
    <div>
      <Pagination
        currentPage={page}
        pageSize={pageSize}
        onPageChange={handlePageChange}
      />
    </div>
  );
}

3️⃣ 검색 기능

사용자의 검색어를 URL에 저장하여 검색 결과를 공유하거나 브라우저 히스토리에서 돌아갈 수 있게 하는 경우

function SearchBar() {
  const [searchParams, setSearchParams] = useSearchParams();
  const query = searchParams.get('q') || '';

  const handleSearch = (e) => {
    e.preventDefault();
    const value = e.target.elements.search.value;
    setSearchParams({ q: value });
  };

  return (
    <form onSubmit={handleSearch}>
      <input name="search" defaultValue={query} />
      <button type="submit">Search</button>
    </form>
  );
}

 

 

성능 최적화 정리

코드 분할(Code Splitting)

불필요한 JS 로딩을 줄여 초기 렌더링 속도 향상

🔹 React.lazy + Suspense

컴포넌트를 동적으로 import 해서 필요한 순간에만 로딩

import React, { Suspense, lazy } from 'react';

const MyComponent = lazy(() => import('./MyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyComponent />
    </Suspense>
  );
}
  • 적용 위치: 무거운 페이지 컴포넌트, 잘 안 쓰는 컴포넌트
  • 이점: 번들 사이즈 ↓, 초기 로드 속도 ↑

 

디바운스/쓰로틀(Debounce/Throttle)

이벤트 과잉 호출 제어 - 자원 낭비 방지, UX 개선

✅ 디바운스(Debounce)

이벤트가 끝난 후 일정 시간 뒤 한번만 실행, 검색 입력 후 API 호출 등에 사용

const handleSearch = debounce((keyword) => {
  fetchData(keyword);
}, 500);

✅ 쓰로틀(Throttle)

이벤트가 일정 간격마다 최대 한 번 실행, 스크롤 위치 감지/윈도우 resize 등에 사용

const handleResize = throttle(() => {
  console.log(window.innerWidth);
}, 300);
728x90
반응형

'LG 유플러스 유레카 SW > React' 카테고리의 다른 글

[#62] 용돈 기입장 프로젝트 (feat. React)  (0) 2025.04.28
[#61] Redux란  (1) 2025.04.24
[#59] React 쇼핑몰 - Shop 페이지 상품 필터링/정렬 + 페이지네이션  (0) 2025.04.22
[#58] React 쇼핑몰 - 장바구니 수정/삭제 + 반응형 Shop 페이지  (0) 2025.04.21
[#57] React 쇼핑몰 - 상품 상세 페이지(탭, 슬라이더) + 장바구니 조회/추가  (0) 2025.04.18
'LG 유플러스 유레카 SW/React' 카테고리의 다른 글
  • [#62] 용돈 기입장 프로젝트 (feat. React)
  • [#61] Redux란
  • [#59] React 쇼핑몰 - Shop 페이지 상품 필터링/정렬 + 페이지네이션
  • [#58] React 쇼핑몰 - 장바구니 수정/삭제 + 반응형 Shop 페이지
nueos
nueos
  • nueos
    nueos 공부 기록
    nueos
  • 전체
    오늘
    어제
    • 분류 전체보기 (191)
      • 해커톤 (1)
      • 네이버 BoostCamp (6)
      • LG 유플러스 유레카 SW (3)
        • React (21)
        • TypeScript (2)
        • JavaScript (2)
        • HTML+CSS (5)
        • Spring (7)
        • Java (6)
        • SQL (2)
        • Algorithm (8)
        • CX (6)
        • Git (2)
        • 프로젝트 (2)
        • 스터디 (9)
        • 과제 (8)
        • 특강 (1)
      • React (3)
      • Next (0)
      • Javascript (2)
      • HTML (2)
      • CSS (9)
      • Algorithm (6)
      • Database (0)
      • OS (13)
      • C++ (24)
      • Python (1)
      • jQuery (1)
      • Django (1)
      • Git (1)
      • 개발 지식 (3)
      • 정보 보안 (22)
      • 포렌식 (1)
      • 암호 (2)
      • 기타 (4)
      • 패스트캠퍼스 FE 프로젝트십 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    디지랩챌린지
    제주해커톤
    힙
    큐
    제주지역혁신플랫폼지능형서비스사업단
    Queue
    완전 탐색
    스택
    Stack
    exhaustive search
    heap
    기술로바꾸는세상
    디지털혁신
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
nueos
[#60] React - Hook + 성능 최적화 정리
상단으로

티스토리툴바