ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • React 딥다이브
    프론트엔드 2025. 10. 17. 20:11

    리액트 훅 파해치기

    useState

    • useState 컴포넌트는 내부에서 상태를 정의하고, 이 상태를 관리할 수 있게 해주는 훅이다 
    import { useState } from 'react'
    
    const [state, setState] = useState(initialState);
    • useState 인수로는 사용할 초기값 state 초기값을 넘겨준다. 아무런 값을 넘겨주지 않으면 undefined가 나온다. 
    • useState 훅의 반환 값은 배열이며, 배열의 첫번째 원소로 state 값 자체를 사용이 가능하고, 두번째 setState 함수를 사용해 state의 값을 변경이 가능하다.
    function Component() {
      let state = 'hello'
    }
    
     function handleButtonClick() {
       state = 'hi'
     }
    
     return (
       <>
         <h1>{state}</h1>
         <button onClick={handleButtonClick}>hi</button>
       </>
     )
    }
    • 위 코드는 리액트에서 동작하지 않는다. 리액트에서 렌더링은 return 함수를 실행한 다음, 실행 결과를 이전의 리액트 트리와 비교해 필요한 부분만 업데이트 된다. 리액트에서 리렌더링이 일어나는 조건은 부모컴포넌트가 렌더링 되거나, key 값이 변경이 되거나, props, state가 변경이 되어야 리렌더링이 발생한다. 
    • 위와 같은 이유로 다음 코드는 버튼을 눌러도 아무런 동작을 하지 않는다. 
    function Component() {
       const [, trigger] = useState()
       
       let state = 'hello'
    }
    
     function handleButtonClick() {
       state = 'hi'
       trigger()
     }
    
     return (
       <>
         <h1>{state}</h1>
         <button onClick={handleButtonClick}>hi</button>
       </>
     )
    }

     

    • 그렇다면?? 다음은 코드에서는 setState 함수를 실행시켜 렌더링이 일어나게 했는데 여전히 버튼 클릭해도 아무런 값이 나오지 않는다. 
    • 이유는 return 값을 비교해 실행되기 때문이다. 즉 매번 렌더링이 발생할 때마다 함수는 새롭게 다시 실행되고, 새롭게 실행되는 함수에서 state는 매번 hello로 초기화 되고 아무리 state를 변경해도 hello로 초기화 된다. 
    여기서 알 수 있는 것은 렌더링이 발생하면 컴포넌트 자체는 새롭게 다시 실행된다는 것이다
    function Component() {
      const [state, setState] = useState(false)
      
      function handleClick() {
        setState((prev) => !prev)
      }
      
      return (
        <>
         <button onClick={handleClick}>+</button>
        </>
      )
    }
    • 위처럼 사용해야 정상적으로 작동을 한다. 근데 궁금한게 위에서 말한 렌더링이 발생하면 컴포넌트 자체가 다시 새롭게 실행이 되는데 어떻게 초기화가 안될까 🤔
    ✅ 이유는 리액트 파이버 때문이다. 
    만약 setState 함수가 호출되면 리액트 Fiber에 존재하는 updateQueue에 등록한다. 이를 기반으로 리액트는 다시 컴포넌트를 실행시킨다. 
    setState(nextValue)
    
    Fiber.memoizedState = {        // useState 훅 저장 공간
      memoizedState: currentValue, // 현재 상태값
      updateQueue: [               // 변경 요청들
        { action: nextValue }
      ]
    }​
    setState가 업데이트 되면 아래와 같이 파이버는 updateQueue에 새로운 상태를 적용시키고 이 결과를 memoziedState에 저장한다고 생각하자. 

    게으른 초기화

    • 일반적으로 useState에서 기본값을 선언하기 위해 useState() 인수로 원시값을 넣는 경우가 대부분일 것이다. 그러나 이 useState 인수로 특정한 값을 넘기는 함수를 넣어줄 수 있다. useState에 변수 대신 함수를 넘기는 것을 게으른 초기화(lazy initailzation)이라 한다. 
    • 리액트 공식문서에서 lazy initailzation은 복잡하거나 무거운 연산을 포함하고 있을때 사용하라고 되어있다. 
    • 이 초기화 함수는 오로지 state가 처음 만들어질때만 사용하고 리렌더링이 발생하면 무시된다고 한다. 

    • 게으른 초기화는 정리하면 useState 인수로 함수를 전달해야할 때를 방지하고자 사용하면 된다고 알고있자!!

    ⚠️ 주의사항으로는 lazy initailzation에 함수 자체를 넣어줘야지 함수를 호출하는 식으로 만들면 리렌더링되면 함수가 호출되니 이를 주의하자

     

    useEffect

    • useEffect는 애플리케이션 내 컴포넌트들의 여러 값들을 활용해 동기적으로 부수 효과를 만드는 매커니즘이다. 
    useEffect(() => {
      
    }, [props, state])
    • useEffect의 첫 번째 인수로는 실행할 부수효과가 있는 함수를, 두 번째 인수로는 의존성 배열을 전달한다. 
    • 의존성 배열이 변경될때 콜백함수가 실행이 된다고 하는데 어떻게 실행이 되는걸까..?? 🧐 
    import React, { useEffect } from "react";
    
    export default function Footer() {
      const [counter, setCounter] = useState(0)
    
      useEffect(() => {
        console.log(counter) // 1 2 3 4...
      }, []);
      
      useEffect(() => {
        console.log(counter) // 1 2 3 4...
      }, [counter]);
    
      useEffect(() => {
           console.log(counter) // 1 2 3 4...
      }, [counter]);
      
      useEffect(() => {
      console.log("🎬 effect 실행");
    
      return () => {
        console.log("🧹 cleanup 실행");
      };
    }, []);
    
    
    
      return <button onClick={handleClick}>Click me</button>;
    }
    • useEffect는 state와 props의 변화속에서 일어나는 렌더링 과정에서 실행되는 부수 효과 함수라고 볼 수 있다. 
    • 위 useEffect 훅에서 의존성 배열은 빈배열[] 이다. 이러면 useEffect는 화면에 처음 마운트 되었을때만 렌더링을 실행한다. state, props가 변경이 되어도 한번만 실행이 된다. 
    • 다음 useEffect의 의존성 배열에는 [counter] state가 들어있는데, 이는 state의 값이 변경될 때 리렌더링이 발생하는 의미이다. 

    clean up 함수

    • clean up 함수는 말그대로 정리를 위한 함수이다. 
    • 위 예제에서 외부 API 등록하거나, setTimeout등 useEffect 안에서 side effect가 실행이 되면 브라우저는 메모리 누수가 생길 수 있다. 
    • 무조건 적으로 useEffect를 사용했다고 해서 clean up 함수를 사용해야 하는 것은 아니다. 

     

    useEffect 조금더 깊이 파보기 

    - useEffect가 side effect를 처리하기 위해서 만든거는 알겠다.. 🧐 근데 side effect나 이런 외부에서 일어나는 일들을 리액트 내부에서 처리하지 않을까?? 
    - 이는 React가 순수하지 않은 작업을 commit Phase 단계에서 실행을 하고 싶어서라고 한다. 
    - React는 UI 렌더링과 side effect를 완전히 분리하고 싶어 한다. 

    이유는 리액트 렌더링 단계에서 렌더 단계에서 JSX를 계산할 때 예측 가능한 렌더링을 유지하고 싶어서 라고 한다. 
    이로 인해 side effect가 일어나는 경우 렌더 단계에서 실행이 되는 것이 아니라 커밋 단계에서 실행이 된다 !!! 

    🧐 그렇다면 모든 훅들???도 커밋 단계에서 실행이 될까??? 
    - 정답은 모든 훅은 아니다. side effect를 일으키지 않는 훅들은 커밋 단계가 아닌 렌더 단계에서 실행이 된다. 

    useEffect => Passive Effect? 
    - 리액트에서 useEffect를 수동 효과 라고도 하는데 브라우저의 DOM 업데이트와 페인팅을 모두 끝난 후에 비동기로 실행이 되기 때문이다. 
    - 리액트에서 내부적으로 Fiber Tree를 돌면서 모든 useEffect들을 한번에 실행하지 않고 queue에 넣어둔다. 이를 Passive Effect Queue라고 한다. 

     

    📘 정리

    1️⃣ useState — 상태의 저장소, Fiber에 의존하는 이유

    💡 정의

    useState는 컴포넌트 내부에서 상태(state)를 정의하고 관리하기 위한 훅이다.

    import { useState } from 'react';
    
    const [state, setState] = useState(initialState);
     
    • initialState: 초기 상태 값 (넘기지 않으면 undefined)
    • 반환값: [state, setState]
      • state: 현재 상태 값
      • setState: 상태를 갱신하고 리렌더링을 트리거하는 함수

     

    2️⃣ useEffect — 부수효과(Side Effect)를 관리하는 메커니즘

    💡 정의

    useEffect는 렌더링 과정에서 발생하는 부수효과(side effect)를 다루는 훅이다.

    useEffect(() => {
    // 부수효과 로직
    return () => { // cleanup 로직 };
    }, [deps]);
    • 첫 번째 인수: 실행할 함수
    • 두 번째 인수: 의존성 배열(deps)
      • []: 최초 마운트 시 한 번만 실행
      • [state]: 해당 값이 바뀔 때마다 실행

    '프론트엔드' 카테고리의 다른 글

    React 딥다이브  (0) 2025.10.20
    프로젝트를 하면서 고민점들 (라우팅 렌더링)  (0) 2025.10.18
    React 딥다이브  (1) 2025.10.16
    React 딥다이브  (0) 2025.10.15
    React 딥다이브  (0) 2025.10.15
Designed by Tistory.