ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • useState, useRef, useEffect
    카테고리 없음 2025. 11. 11. 09:49

    ✅ 1. useState  화면에 보여지는 값을 저장

    ✅ 개념

    • 화면을 다시 그리게 만드는 상태값을 저장
    • 값이 바뀌면 컴포넌트가 리렌더링

    ✅ 비유

    "게시판 글 수", "내가 입력한 텍스트", "다크모드 on/off" 같은 화면에 영향을 주는 값

    ✅ 사용 예시

     
    import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); // count = 상태 값, setCount = 수정 함수 return ( <div> <p>현재 카운트: {count}</p> <button onClick={() => setCount(count + 1)}> +1 </button> </div> ); }

     count 값이 바뀌면 화면이 다시 렌더링됨


    ✅ 2. useRef  눈에 안 띄는 값을 저장

    ✅ 개념

    • 값이 바뀌어도 렌더링이 일어나지 않는 저장공간
    • 주로 DOM 요소 선택, 변하지 않는 값 저장에 사용

    ✅ 비유

    화면에 안 보이게 몰래 저장하는 주머니
    예) "버튼이 몇 번 클릭됐는지", "input DOM을 가져오기"

    ✅ DOM 접근 예시

     
    import { useRef } from "react"; function InputFocus() { const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); // DOM에 직접 접근 }; return ( <div> <input ref={inputRef} /> <button onClick={focusInput}>포커스 주기</button> </div> ); }

    ✅ DOM 제어 필요할 때 useRef


    ✅ 3. useEffect  렌더링 이후 실행되는 함수

    ✅ 개념

    • 화면이 렌더링된 후 실행되는 “사후 처리”용
    • API 호출, 이벤트 등록, 타이머, 로컬스토리지 저장 등에 사용

    ✅ 비유

    “컴포넌트 만들어지고 난 뒤 실행해야 하는 일”


    ✅ 기본 예시 — 컴포넌트 처음 실행될 때 한 번만

     
    import { useEffect } from "react"; useEffect(() => { console.log("처음 화면에 나타났습니다!"); }, []); // 빈 배열이면 최초 1번만 실행

    ✅ 값이 바뀔 때마다 실행

     
    const [count, setCount] = useState(0); useEffect(() => { console.log(`count가 변경됨 : ${count}`); }, [count]); // count가 바뀔 때 실행

    ✅ 언마운트 시 정리(clean-up)

     
    useEffect(() => { const timer = setInterval(() => console.log("타이머"), 1000); return () => { clearInterval(timer); // 컴포넌트 사라질 때 정리 }; }, []);

    ✅ 한 줄 정리

    Hook언제 씀?값이 바뀌면 리렌더링?주 사용 목적
    useState 화면에 영향을 주는 상태 ✅ Yes UI 업데이트
    useRef 렌더링 필요 없는 값, DOM 선택 ❌ No DOM 제어, 임시 저장
    useEffect 렌더링 후 실행해야 하는 작업 - API, 이벤트, 타이머

     

    좋아, 요청한 3가지를 실무 감각으로 딱 이해되게 정리해줄게.


    ✅ 1) useEffect 무한 렌더링 나는 경우

    ✅ 왜 생기나?

    useEffect 안에서 state를 업데이트(setState) 하는데,
    `dependency 배열([])에 그 state를 넣었을 때 계속 반복 호출되기 때문.

    ❌ 잘못된 예 (무한 렌더링)

     
    const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }, [count]);

    동작

    1. 처음 렌더링 → count = 0
    2. useEffect 실행 → setCount(1) → 렌더링
    3. count가 바뀜 → useEffect 다시 실행 → 또 setCount
    4. 계속 반복 → 무한 렌더링 ❌

    ✅ 해결 방법

    ✅ 1. 의존성을 없애고 한 번만 실행하고 싶다면 → []

     
    useEffect(() => { setCount(c => c + 1); }, []);

    ✅ 2. 조건을 걸어서 변경 관리

     
    useEffect(() => { if (count < 5) { setCount(count + 1); } }, [count]);

    ✅ 3. state 업데이트는 연산 결과만 effect에서 처리 (비추천되는 setState 남발 제거)


    ✅ 2) useState vs useRef — 어떤 상황에서 골라 쓰는지?

    비교 요소useStateuseRef
    값 변경 시 리렌더링 ✅ Yes ❌ No
    언제 사용? UI에 영향을 주는 값 UI와 관계없는 값
    예시 입력한 글자, 테마 모드, 모달 열림/닫힘 DOM 선택, 이전 값 저장, 타이머 id

    ✅ 쉽게 구분하는 기준

    이런 상황이면사용
    값이 바뀌면 화면이 바뀌어야 한다 useState
    값이 바뀌어도 화면이 바뀔 필요가 없다 useRef
    DOM 요소에 접근해야 한다 useRef
    setState 쓰면 리렌더링이 너무 많이 일어난다 useRef

    예시

    ✅ 상태는 바뀌지만 화면에서 안 보여야 할 값
    (예: API 호출 횟수, 버튼 클릭 누적, 스크롤 위치 기록)

     
    const clickCount = useRef(0); function handleClick() { clickCount.current += 1; // UI에 안 보임 }

    ✅ DOM 조작

     
    const inputRef = useRef(null); <input ref={inputRef} />

    ✅ 화면에 보여줄 값

     
    const [name, setName] = useState('');

    ✅ 3) 실무에서 가장 많이 쓰는 패턴 정리

     API 호출 + 로딩/에러 상태 관리

     
    const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { const res = await fetch('/api/user'); const result = await res.json(); setData(result); setLoading(false); }; fetchData(); }, []);

     스크롤/resize 이벤트 등록 & cleanup

     
    useEffect(() => { const onScroll = () => console.log("scrolling..."); window.addEventListener("scroll", onScroll); return () => window.removeEventListener("scroll", onScroll); }, []);

     타이머 + component unmount 시 정리

     
    useEffect(() => { const timer = setInterval(() => console.log("tick"), 1000); return () => clearInterval(timer); }, []);

     useRef로 이전 값 기억하기

     
    const prevValue = useRef(); useEffect(() => { prevValue.current = value; }, [value]);

     input DOM 제어

     
    const inputRef = useRef(null); const focusInput = () => { inputRef.current.focus(); };

     State 대신 Ref로 렌더링 줄이기
    (예: 사용자가 키 입력할 때마다 렌더링 막고 싶을 때)

     
    const typingValue = useRef(""); function onChange(e) { typingValue.current = e.target.value; }

    ✅ 요약 한 번에 끝내기

    ✅ 무한 렌더링 생길 때
    → useEffect 안에서 setState + dependency에 그 state 넣었을 때

    ✅ useState vs useRef

    • 화면 업데이트 필요하면 → useState
    • 업데이트 필요없고 값만 유지하거나 DOM 접근 → useRef

    ✅ 실무에서 가장 많이 쓰는 패턴

    • API 호출
    • 이벤트 등록/정리
    • 타이머 관리
    • DOM 접근
    • 렌더링 줄이기 용도로 useRef 사용

    ✅ 실제 예 — 3가지 함께 쓰기

     
    import { useState, useEffect, useRef } from "react"; function Example() { const [count, setCount] = useState(0); const renderCount = useRef(1); useEffect(() => { renderCount.current += 1; console.log(`렌더링 횟수: ${renderCount.current}`); }); return ( <div> <p>count: {count}</p> <button onClick={() => setCount(count + 1)}> +1 </button> </div> ); }

     count가 바뀌면 화면 다시 그림 (useState)
    ✅ 렌더링 횟수는 ref로 저장되며 화면엔 영향 없음
    ✅ 렌더 후마다 Effect 실행됨


    필요하면 더 확장해서 이런 것도 알려줄 수 있어:

     useEffect 무한 렌더링 나는 경우
    ✅ useState·useRef 어떤 상황에서 각각 골라 쓰는지
    ✅ 실무에서 제일 많이 쓰는 패턴 정리

     

    ✅ 그럼 코드에서 "언마운트 시"는 어디야?

    바로 useEffect의 return() 내부 👇

     
    useEffect(() => { const timer = setInterval(() => console.log("타이머"), 1000); return () => { clearInterval(timer); // ✅ 여기가 언마운트 시 실행되는 코드 }; }, []);

    ✅ 정리하자면:

    • useEffect의 return에 담긴 함수 = cleanup(정리용 함수)
    • 이 함수는 컴포넌트가 사라질 때 자동 실행

    ✅ 실제 예로 이해하기

    예를 들어 페이지 이동할 때:

    ✅ Home.jsx → Timer.jsx로 갔다가
    ❌ 다시 Home.jsx로 돌아오면

    ➡ Timer 컴포넌트는 사라짐(unmount)
    ➡ 타이머 계속 돌면 메모리 낭비 / 콘솔 도배
    ➡ 그래서 return 안에서 제거해야 함


    ✅ 비유

    • 타이머 켰으면 → 나갈 때 끄고 나가는 게 정상
    • 이벤트리스너 등록했으면 → 떠날 때 제거
    • WebSocket 연결했으면 → 사라질 때 끊기

    이 행동을 자동으로 실행시켜주는 위치 = return() 내부


    ✅ 추가 예시 (스크롤 이벤트 정리)

     
    useEffect(() => { const handleScroll = () => console.log("스크롤!"); window.addEventListener("scroll", handleScroll); return () => { // ✅ 컴포넌트 사라질 때 이벤트 제거 window.removeEventListener("scroll", handleScroll); }; }, []);

    혹시,

    • 언마운트 상황이 실제로 언제 발생하는지?
    • useEffect cleanup이 없을 때 어떤 문제가 생기는지?
    • 렌더될 때마다 cleanup 실행되는 케이스?

    이런 것도 궁금하면 더 자세히 예제로 알려줄게!

     

     

    ✅ 0) .current는 뭐야?

     .current는 useRef가 만들어주는 일반 객체의 프로퍼티(값 저장공간) 이다.

     
    const ref = useRef(0); console.log(ref); // { current: 0 }
    • 특별한 메서드(method)가 아님 ❌
    • 그냥 { current: 원하는 값 } 형태의 객체
    • 우리가 값을 마음대로 바꿀 수 있는 저장칸
     
    ref.current = 10;

    ✔ useRef는 항상 이렇게 생긴 하나의 객체를 반환할 뿐(불변 아님)
    ✔ 이 객체는 컴포넌트가 리렌더링될 때도 사라지지 않음


    ✅ 1) "setState 전에 저장하면 되지 않나?" 왜 안될까?

    보통 이런 생각이 든다:

     
    setPrev(value); // 이전 값 저장 setValue(newValue);

    겉으로 보면 맞는 것 같지만…

    ❌ 문제: setState는 비동기
    즉, 호출했다고 바로 값이 바뀌는 게 아니라
    React가 "렌더링 스케줄"을 잡아서 나중에 변경함


    ❌ 잘못된 코드 예

     
    const [value, setValue] = useState(0); const [prev, setPrev] = useState(0); function handleClick() { setPrev(value); setValue(value + 1); console.log("이전값: ", prev); // ❌ 기대한 값이 안 나올 수 있음 }

    왜 문제?

    setPrev도, setValue도 렌더링이 일어나야 값이 바뀜
    즉:

    1. 버튼 클릭
    2. setPrev(value) 실행
    3. setValue(value + 1) 실행
    4. 아직 둘 다 반영되지 않음
    5. 그대로 console.log 실행 → 최신값이 아님

    ✅ 결론: state는 즉시 바뀌지 않는다
    ➡ "지금" 정확하게 이전 값을 저장하고 싶다면 state로는 못함
    ➡ 그래서 ref를 쓰는 것


    ✅ 2) "그럼 useState로 이전 값 저장하면 뭐가 문제?"

    ✅ 리렌더링이 계속 발생

     
    const [prev, setPrev] = useState(0);

    setPrev(…)를 호출하면 → 리렌더링 발생
    리렌더링 → effect 실행 → 또 setPrev → 또 리렌더
    → 무한 렌더링 가능성 ↑

    ✅ 성능 낭비

    “이전 값”은 화면에 보여줄 필요가 없는데
    state로 두면 불필요한 렌더링이 생김

    ✅ ref는 값이 바뀌어도 화면을 다시 그리지 않음
    → 딱 기억만 하는 용도로 적합


    ✅ 3) 비동기 업데이트 상황에서 왜 ref가 더 안정적?

    state는 ❗비동기 + 묶어서(batch) 처리됨

    즉 하나씩 바로바로 바뀌는 게 아니라
    React가 “묶어서 한 번에” 렌더링할 수 있음

    ➡ 그렇다면 이런 상황

     
    setValue(value + 1); setValue(value + 1); setValue(value + 1);

    기대값? 3 증가
    하지만 실제론 1만 증가할 때가 있음

    그래서 안전하게 하려면:

     
    setValue(v => v + 1);

    즉 콜백 패턴 써야 함


    ✅ 그런데 ref는?

    • 그냥 객체일 뿐
    • 비동기, batch 처리 영향을 받지 않음
    • 그 순간 즉시 바뀜
     
    ref.current = ref.current + 1; // 즉시 반영

    ➡ “지금 당장, 확정된 값”을 저장하는데 더 정확함
    ➡ 렌더링 없이도 값이 유지됨

    그래서 이전 값, 누적 값, 이벤트 횟수, 타이머 ID 같은 건 ref가 정답


    ✅ 정리 한 컷

    비교useStateuseRef
    값 바뀌면 렌더링됨 ✅ Yes ❌ No
    비동기 업데이트? ✅ 영향을 받음 ❌ 안 받음
    batch 처리 적용? ✅ Yes ❌ No
    즉시 값 저장 가능? ❌ 때때로 예상과 다름 ✅ 즉시 저장
    이전 값 저장에 적합? ❌ 비효율적 ✅ 최적

    ✅ 이해 완전하게 되는 실제 예

     
    function Example() { const [value, setValue] = useState(0); const prev = useRef(0); const increase = () => { prev.current = value; // ✅ "지금"의 값을 정확히 기억 setValue(value + 1); // ✅ state는 나중에 반영 }; return ( <> <p>현재값: {value}</p> <p>이전값: {prev.current}</p> <button onClick={increase}>+1</button> </> ); }

    ✅ 매번 클릭할 때마다 이전 값이 정확하게 유지됨
    ✅ 화면에 보여줘야 하는 값(value)은 state
    ✅ 화면에 안 보여도 기억해야 하는 값(prev)은 ref

     

    "이전값을 어디에 저장해야 맞는가?"
    → 정답이 하나가 아니라 상황에 따라 다르다.
    아래 기준만 알면 항상 정확하게 판단할 수 있어.


    ✅ 1) 컴포넌트 내부에서만 필요하다 → 자식(useRef 또는 useState)

    ✅ 가장 흔한 경우

    • 이전 값이 그 컴포넌트 안에서만 쓰임
    • 다른 컴포넌트가 알 필요 없음
    • 화면에 표시하거나 비교만 하면 됨

    ➡ 이럴 땐 자식 내부에 저장 (useRef)

     
    function Product({ price }) { const prevPrice = useRef(price); useEffect(() => { console.log(prevPrice.current, price); prevPrice.current = price; }, [price]); return <div>{price}</div>; }

    ✔ 가장 간단
    ✔ 렌더링을 일으키지 않음
    ✔ 다른 곳에서 접근 불필요

     이 경우 = 자식 안에 저장이 정답


    ✅ 2) 자식에서 계산하고, 부모가 알아야 한다 → 부모 state

    언제?

    • 부모가 이전값을 기준으로 다른 UI를 바꾸고 싶다
    • 다른 컴포넌트도 이전값이 필요하다

    ➡ 이럴 땐 부모 state에 저장

     
    function App() { const [price, setPrice] = useState(10000); const [prevPrice, setPrevPrice] = useState(10000); const increase = () => { setPrevPrice(price); setPrice(price + 5000); }; return ( <> <Product price=
    728x90

    댓글

Designed by Tistory.