1. Hook 이란 무엇인가?
함수영 컴포넌트에서 React의 기능(상태관리, 생명주기 등)을 사용할 수 있게 해주는 함수입니다
Hook의 기능은 과거 클래스에서만 사용할 수 있었는데 이 기능을 함수형 컴포넌트에서 사용할 수 있게 되면서 업데이트된 기능이 바로 Hook입니다
2. Hook의 종류
- useState : 상태관리
- useEffect : 생명주기 관리
- useContext : 전역 상태 접근
- useRef : DOM 제어 및 상태 값 저장
- useMemo : 값 메모이제이션
- useCallback : 함수 메모이제이션
- useReducer : 복잡한 상태 로직 관리
등이 있으며 현재 18 버전 이후로 새로운 훅들이 추가되었습니다
3. 훅의 특징
- use로 시작해야 합니다
- 함수 컴포넌트 및 커스텀 훅 내부에서만 사용 가능하비다
- 조건문, 반복문 안에서 호출하면 안 됩니다!!
4. Hook 사용해 보기
- useState (상태관리)
상태관리 훅은 리액트의 Hook 중에서도 가장 기본이면서 매우 중요한 Hook입니다
컴포넌트 내에서 상태를 만들고 관리할 수 있는데 상태관리 Hook은 상태값이 변경되면 컴포넌트가 자동으로 다시 렌더링 되는 특징이입니다 즉! setState를 호출할 때마다 렌더링 된다는 말이죠
[사용 예제]
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // 초기값 0으로
return (
<div>
<p>현재 카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
- useState 선언하기
const [count, setCount] = useState(0); // 초기값 0
이 코드에서는 count라는 상태관리 변수를 선언했습니다 count는 get, setCount는 set 역할을 담당하며 useState(0)는 기본값입니다 값을 변경하거나 할당할 땐 setCount(값)으로 할당할 수 있습니다
- useState 사용하기
return (
<main>
<h1>Content</h1>
<input
type="number"onChange={(e) => {setCount(e.target.value);}}/>
<br />
<span>갯수:{count}</span>
</main>
);
이렇게 작성하면 최초 개수는 0으로 시작하고 input에서 숫자를 변경하면 해당 숫자로 개수가 변경되는 걸 알 수 있습니다!
왜 숫자가 변경되는 걸까요?
우선 const [count,setCount] 로 상태관리변수를 생성했습니다 그리고 Jsx코드에의 input의 onChange 이벤트를 통해 입력값이 변경될 때마다 이벤트를 호출하고 호출한 이벤트에선 호출될 때마다 자신의 value 값을 상태값에 업데이트하고 있습니다
※ 잠깐! 상태관리 변수는 값이 업데이트될 때마다 컴포넌트를 업데이트시킨다고 앞서 말했습니다 그러면 어떻게 될까요? 말 그대로로 숫자가 바뀔 때마다 페이지는 새로고침 되지 않지만 컴포넌트는 열심히 리렌더링 되고 있는 겁니다 그래서 {count} 상태변수가 바뀐 숫자 그대로 변경되는 걸 알 수 있습니다
- useEffect (생명주기)
리액트는 3단계의 생명주기를 갖고 있습니다생성될 때, 업데이트할 때, 제거할 때 이런 3단계 과정을 라이프사이클이라고 합니다
그럼 3단계 라이프사이클의 실제 사용예제를 살펴보겠습니다
- useEffect 사용해 보기
컴포넌트가 화면에 처음 나타날 때만 호출하는 useEffect
import { useEffect } from "react";
function Hello() {
useEffect(() => {
console.log("컴포넌트가 화면에 나타났습니다!");
}, []);
return <h1>Hello!</h1>;
}
useEffect(..., []) 코드에서 [] 에 의존성 배열을 넣지 않으면 처음 한 번만 수행합니다 반대로 의존성 배열을 넣게 되면 해당 의존성 배열의 값이 변경될 때마다 useeffect가 실행됩니다 의존성 배열은 state, props 함수 등 꼭 변경 가능성이 있는 값을 넣어야 합니다 복잡한 객체 또는 배열을 넣으면 내용은 같아도 참조가 바뀌기 때문에 매번 실행될 가능성이 있어 주의가 필요해요
즉 간단하게 말하자면 useEffect(() => {...}, [state]) 라는 형태는 state라는 값이 바뀔 때만 실행되도록 리액트야 쟤(state) 감시해 봐라고 하는 것과 같습니다
의존성 배열을 사용한 상태추적 useEffect ( 어떤 특정한 값이 바뀔 때마다 무언가를 하고 싶을 때 )
import { useEffect,useState } from "react";
function Hello() {
const [count,setCount] = useState(0); //상태관리변수
useEffect(() => {
console.log("컴포넌트가 처음 화면에 나타났습니다!");
}, [count]); // 빈 배열이면 '처음 한 번만' 실행됨! 지금은 count를 감시중
return <h1>Hello, React!</h1>;
}
export default Hello;
의존성 배열로 count 상태변수를 지정하여 감시하도록 했습니다 이제 count 상태변수의 값이 변경되면 useEffect가 호출됩니다
※ 여러 개 상태를 동시에 감시하려면 어떻게 해야 할까? 🤔
const [count, setCount] = useState(0);
const [text, setText] = useState("");
useEffect(() => {
console.log("count 또는 text 중 하나라도 바뀌면 실행됩니다!");
console.log("count:", count);
console.log("text:", text);
}, [count, text]); // 둘 중 하나라도 바뀌면 실행
이렇게 의존성 배열에 변화를 추적할 상태를 추가해 주면 추적하면서 값이 변경될 때마다 호출됩니다
컴포넌트가 사라질 때 useEffect (돔에서 언 마운트 될 때)
import { useEffect, useState } from "react";
function Timer() {
useEffect(() => {
const timer = setInterval(() => {
console.log(" 1초마다 실행 중...");
}, 1000);
// 컴포넌트가 사라질 때 실행됨 (정리함수)
return () => {
console.log(" 타이머 정리됨 (언마운트 시)");
clearInterval(timer);
};
}, []);
return <h2>타이머 작동 중 (콘솔을 확인하세요)</h2>;
}
이 코드에서는 컴포넌트를 닫아도 setInterval 이 계속 동작할 수 있는 문제를 방지하기 위해 컴포넌트가 사라질 때 clearInterval를 사용해 setInterval을 clear 시켜 작동을 멈추게 합니다 useEffect 내 return () => {...} 코드에서 언 마운트에 대한 동작을 설정할 수 있습니다
※ 언 마운트 시에도 의존성 배열을 사용하나요?
무엇을 개발하는지 어떻게 흘러가야 하는지 틀리겠지만 의존성을 붙이면 업데이트도 가능하고 사라질 때도 호출되는 기능의 useEffect를 만들 수 있겠죠! 수시로 업데이트를 해야 하면서 컴포넌트가 사라질 때만 특정 동작을 해야 한다면 같이 사용하면 좋을 것 같아요
※ 언 마운트가 일어나는 순간이 언제냐?!
{show && <Timer />} // show가 false가 되면 <Timer />는 언마운트됨
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
// Home 에서 About으로 이동 시 Home은 언마운트됩니다 다른페이지로 이동했기때문에..!
{isOpen && <Modal />} // 모달이 닫히면 <Modal />이 언마운트됨
컴포넌트가 브라우저 상에서 사라지면 언마운트입니다! 물론 css으로 요소를 hidden 시키는 건 마운트가 아닙니다!
- useMemo (메모이제이션)
복잡하거나 비용이 큰 계산 결과를 기억해서 불필요하게 다시 계산하지 않도록 하는 Hook입니다
const memoizedValue = useMemo(() => {
// 계산할 내용
return result;
}, [의존성]);
예를 들어 useEffect에서 리 렌더링을 하는데 매번 복잡한 계산을 하게 되는 경우가 있습니다 하지만 결과가 똑같을 경우엔 오히려 성능에 악영향을 줄 수 있는데 이때 결과 값을 기억해 성능을 최적화하는 역할을 합니다
리렌더링 될 때 계산을 다시 하지 않아도 되는 경우
성능이 중요한 경우
계산 결과가 값(숫자, 문자열, 배열, 객체 등) 일 때
useMemo를 사용한 간단한 예제
import { useState, useMemo } from "react";
function SlowComponent({ count }) {
// 아주 무거운 연산을 만들어봄!
const expensiveResult = useMemo(() => {
console.log("계산 중입니다..");
let total = 0;
for (let i = 0; i < 1_000_000_000; i++) {
total += i;
}
return total + count;
}, [count]);
return <p>계산 결과: {expensiveResult}</p>;
}
굉장히 큰 수를 연산하는 자원이 많이 소모되는 작업입니다 이 작업이 매번 컴포넌트가 렌더링, 리렌더링 될 때마다 실행된다면 성능이 심각하게 저하가 될 겁니다 하지만 의존성 배열에 특정 값을 추적하고 업데이트가 일어났을 때만 다시 계산을 하도록 변경한다면? 불필요한 작업이 줄어들고 그로 인한 성능은 더 좋아지게 됩니다 이럴 때 사용하는 게 useMemo 입니다 useMemo를 사용할 땐 반드시 의존성 배열을 지정한다고 생각하면 됩니다
의존성 배열을 지정하지 않으면 최초 렌더링, 리렌더링 매번 호출되어 useMemo를 사용하는 의미가 없기 때문!
- React.memo (고차 컴포넌트)
React.memo는 props가 바뀌지 않았다면 컴포넌트를 다시 렌더링 하지 않도록 해주는 고차 컴포넌트입니다
즉 "props가 그대로면 리렌더링을 하지 않는다!"
함수형 컴포넌트는 부모가 리렌더링 되면 자식도 무조건 리렌더링 되는 특징이 있습니다
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<Child text="안녕" /> {/* props 안 바뀌었는데도 렌더링됨! */}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Child라는 자식 컴포넌트를 Parent 컴포넌트가 감싸고 있습니다 부모컴포넌트인 Parent 컴포넌트에서 상태값이 변경되어 업데이트가 되는데 자식 컴포넌트내부에선 업데이트된 내용이 없음에도 매번 부모를 따라 불필요한 리렌더링이 되고 있습니다
이때 불필요한 리렌더링을 줄이고 성능을 최적화할 수 있는 방법이 React.memo 입니다 React.memo는 컴포넌트 생성방법이 약간 틀려요!
//원래 함수 컴포넌트 생성문법
import React from 'react';
const Wight02_Chlidrun = () => {
return (
<div>
<p>원래 문법입니다</p>
</div>
);
};
export default Wight02_Chlidrun;
//React.memo 사용 문법
import React, { useEffect } from "react";
// React.memo 로 감싸서 props가 바뀌지 않으면 렌더링 방지
const Wight02_Children = React.memo(function Wight02_Children({
onClick,
label,
}) {
useEffect(() => {
console.log("자식이 마운트 되었습니다.");
return () => {
console.log("자식이 언마운트 되었습니다.");
};
}, []);
return <button onClick={onClick}>자식 버튼: {label}</button>;
});
export default Wight02_Children;
React.memo(function Wight02_Children({
props...
}) {...}로 정의합니다
본 코드에서는 리렌더링 여부를 콘솔로 확인하기 위해 useEffect를 추가하였습니다 만약 리렌더링이 진행된다면 "자식이 마운트 되었습니다"가 나와야 합니다
import React, { useState, useCallback } from "react";
import Wight02_Children from "../components/Wight02_Children";
function Parent() {
const [count, setCount] = useState(0);
//useCallback으로 함수 메모이제이션
const handleClick = useCallback(() => {
console.log("자식 버튼 클릭!");
}, []);
return (
<div>
<h2>부모 컴포넌트</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>부모 +1</button>
{/*자식에게 props 전달 */}
<Wight02_Children onClick={handleClick} label="나는 자식이에요" />
</div>
);
}
export default Parent;
부포 컴포넌트에서는 기존에 사용하던 방식으로 작성할 수 있습니다 생각보다 간단한 방법으로 성능최적화 그리고 UX를 개선할 수 있는 기능을 제공하였습니다 이 코드에선 useCallback 을 사용하여 함수메모이제이션을 사용했는데 다음 내용으로 함수이제이션을 알아보겠습니다
- useCallback (함수메모이제이션)
다시 되돌아가서 useMemo는 어떤 기능이었나요? 보통 자원소모나 부하가 큰 작업에서 사용하면서 값의 변화를 체크하고 변화가 없으면 최초 계산한 결괏값을 그대로 반환하는 역할을 합니다
useCallback은 값이 아닌 함수를 기억합니다 같은 작업을 하는 함수를 리렌더링 할 때마다 새로 만들지 않고, 이전에 만든 함수를 재사용합니다
기본 문법
const memoizedFuction = useCallback(() => {
// 어떤 작업
}, [의존성]);
function Parent() {
const handleClick = () => console.log("clicked");
return <Child onClick={handleClick} />;
}
이 코드를 보면 useCallback이 아닌 일반 함수로 만들어졌습니다 이 경우 마운트될 때, 업데이트될 때, 언마운트될 때 매번 함수가 새로 만들어지게 됩니다
저게 왜 문제가 되나요?🤔
위 코드에서는 간단한 콘솔만 띄우는 작업을 하고 있어서 성능상 크게 무리가 없습니다 그리고 얼핏 보면 한번 마운트 될 때 함수가 여러개가 생성되는게 아닌데 왜 문제가 될까요? 어차피 자식과 부모의 handleClick 함수는 이어져있는거 아닌가요? 라고 할 수 있습니다 이 이유엔 심화적인 이론으로 들어가야합니다 함수와, 변수 모두 만들어 질때 주소값이란 참조값을 부여받습니다 하지만 앞서 마운드 될때 다시 만들어지고, 업데이트될 때 다시 만들어진다면 이 참조값은 또 새로 바뀌고 또 새로 바뀌게 됩니다 그러면 당연히 자식에서도 부모에서 넘겨주는 props의 참조값이 바뀌니 업데이트가 될 겁니다 이렇게 되면 useMemo, React.memo를 사용하는 의미가 없어지게 됩니다
만약 props로 함수를 전달한다면 React.memo 와 useCallback은 하나의 세트라고 봐도 무방하겠죠?👍
다음은 useContext, useRef, useReducer 그리고 Redux에 대해 알아보겠습니다
'WEB > REACT' 카테고리의 다른 글
리액트 복습-2 Layout구성, React Router 사용해보기 (0) | 2025.03.26 |
---|---|
리액트 복습-1 환경구축, 컴포넌트 만들기 (1) | 2025.03.26 |