본문 바로가기
TIL

11. 04. 20일차 TIL - useState, useEffect, useRef, useContext

by 눈 새 2024. 11. 4.

오늘부터 React 숙련 주차 시작이다. 오늘은 제공된 강의를 통해 React에서 많이 사용하는 Hook에 대해 공부하였다.


# 훅 (Hooks)

React Hook은 클래스형 컴포넌트에서 이용하던 코드를 작성할 필요 없이 함수형 컴포넌트에서 다양한 기능을 사용할 수 있게 만들어준 라이브러리로 React 16.8v에서 새로 추가된 기능이다. 이는 함수형 컴포넌트에 맞게 만들어진 것으로 함수형 컴포넌트에서만 사용 가능하다. Hook을 사용하는 데에는 몇 가지의 규칙이 있다.

  • 최상위에서만 Hook을 호출해야한다.
    • 반복문이나 조건문 혹은 중첩된 함수 내에서 Hook을 호출하면 안된다.
    • React Hook은 호출되는 순서에 의존하기 때문에 조건문이나 반복문 안에서 실행하게 될 경우 해당 부분을 건너뛰는 일이 발생할 수도 있기 때문에 버그를 야기할 수 있다.
  • React 함수 내에섬나 Hook을 호출해야 한다.
    • Hook은 일반적인 JS함수에서는 호출하면 안된다.
    • 하지만 함수형 컴포넌트나 custom Hook에서는 호출이 가능하다.

1. useState 

useState는 가장 기본적인 hook이며, 함수 컴포넌트에서 가변적인 상태를 가지게 해준다.

(이를 활용하여 이전 개인과제에서 Table에 데이터를 업데이트하는 기능을 구현할 수 있었다.)

useState의 기본적인 형태

 

주의점으로 만약 State가 원시 데이터 타입이 아닌 객체 데이터 타입인 경우 불변성을 유지해줘야 한다.

 

1) 함수형 업데이트

setState를 사용하는 방식에는 내가 사용한 방법 말고도 함수형 업데이트 방식이 있다.

 

위의 코드와 같이 setState의 ()안에 수정할 값이 아닌 함수를 넣을 수 있다. 그 함수의 인자에서는 현재의 state를 가져올 수 있고 {}안에서는 이 값을 변경하는 코드를 작성할 수 있다.

 

두 방식의 차이점은 무엇일까? 간단한 예시를 통해 확인해보았다.

일반 업데이트 방식
함수형 업데이트 방식

 

두가지 방법 모두 +3을 의도하고 있지만 일반 업데이트 방식에서는 +1씩 증가하고, 함수형 업데이트 방식에서야 +3씩 증가하는 것을 확인할 수 있었다.

 

이는 React의 작동 방식 때문인데 명령을 하나로 모아서 최종적으로 한번만 실행하기 때문이다. 즉, 일반 업데이트 방식으로 100번 그 이상을 호출하더라도 결과는 +1로 같을 것이다. 

 

반면에 함수형 업데이트 방식은 3번을 동시에 명령을 내리면, 그 명령을 모아 순차적으로 각각 1번씩 실행한다. 0에 1을 더하고, 그 다음에 1에 1을 더하고, 그 다음에 2에 1을 더해 3이라는 결과를 얻을 수 있는 것이다.

 

2) useState는 왜 위 방식으로 동작하도록 설계되었을까?

React는 성능을 위해 setState()를 단일 업데이트(batch update)로 한꺼번에 처리할 수 있다.

 

공식문서에서는 불필요한 re-rendering을 방지하기 위해 즉, React의 성능을 위해 한꺼번에 state를 업데이트 하도록 설계되었다고 한다. 이를 통해 렌더링을 최적화 하는것이다.


2. useEffect

useEffect는 React 컴포넌트가 렌더링 된 이후마다 특정 작업을 수행하도록설정할 수 있는 Hook이다.

어떤 컴포넌트가 화면에 보여졌을 때 내가 무언가를 실행하고 싶은 경우, 또는 어떤 컴포넌트가 화면에서 사라졌을 때 무언가를 실행하고 싶은 경우 useEffect를 사용한다.

 

1) useEffect 기초

useEffect는 컴포넌트가 렌더링 된 이후 실행된다. 예시 코드에서는 사용자가 App 컴포넌트를 브라우저를 통해 시작적으로 볼 때(App 컴포넌트가 화면에 렌더링 된 이후) useEffect  안에 있는 console.log가 실행된다.

 

2) useEffect와 리렌더링 (re-rendering)

useEffect는 리렌더링과 밀접한 관련이 있다. useEffect는 리렌더링된 후에 실행되기 때문이다. 즉, 상태나 속성이 변경되어 컴포넌트가 다시 렌더링된 직후에 useEffect의 콜백 함수가 호출된다.

 

3) 의존성 배열

 useEffect에는 의존성 배열이라는 것이 있다. 의존성 배열이란 특정 상태나 속성이 변경될 때에만 useEffect를 실행하도록 설정하는 것을 의미한다. 이를 통해 불필요한 작업을 줄이고 성능을 최적화할 수 있다.

  • 빈 배열 [] : 컴포넌트가 처음 렌더링될 때만 실행
  • 특정 값들 : 해당 값이 변경될 때마다 실행
  • 의존성 배열 없음 : 매 렌더링마다 실행

 

4) 정리 함수 (Clean-Up)

useEffect는 정리 함수를 반환할 수 있다. 이 함수는 컴포넌트가 언마운트되기 전이나 다음 effect가 실행되기 전에 호출된다. 이를 통해 구독 해제, 타이머 정리, 등 리소스를 관리할 수 있다.

 

 

5) 컴포넌트 라이프사이클

React 컴포넌트도 JS의 변수처럼 태어나고, 살아가고, 죽는 생애주기가 존재한다고 한다ㅋㅋ

클래형 컴포넌트를 주로 사용했을 이전 버전에서는 이 생애주기와 관련된 여러 메서드가 존재했으나 현재처럼 함수형 컴포넌트를 사용할 때에는 useEffect를 주로 사용하여 핸들링한다.

  • 마운트 (Mounting) : 컴포넌트가 처음 생성될 때 발생한다.
    • constructor : 컴포넌트가 생성될 대 호출되며, 초기 상태를 설정할 수 있다.
    • render : UI를 정의하는 메서드로, JSX를 반환한다.
    • componentDidMount : 컴포넌트가 DOM에 추가된 직후에 호출된다. 이 때 데이터 패칭, 구독 설정, 등의 작업을 수행 할 수 있다.
  • 업데이트 (Updating) : 컴포넌트의 상태(state)나 속성(props)이 변경될 때 발생한다.
    • shouldComponentUpdate : 컴포넌트가 업데이트될 필요가 있는지를 결정한다. 이는 성능 최적화를 위해 사용한다.
    • render : 컴포넌트의 UI를 다시 렌더링한다.
    • componentDidUpdate : 컴포넌트가 업데이트된 후에 호출된다. 이전 props와 상태를 기준으로 추가 작업을 수행할 수 있다.
  • 언마운트 (Unmounting) : 컴포넌트가 DOM에서 제거될 때 발생한다.
    • componentDidUpdate : 컴포넌트가 제거되기 직전에 호출된다. 구독 해제, 타이머 정리 등의 작업을 수행할 수 있다.

react에서 컴포넌트의 생애주기 단계

 


3. useRef

useRef는 useState와 더불어 특정 값을 저장하기 위해 사용하는 대표적인 hook이다. 리렌더링과 상관없이 값을 기억하기 위해 사용되는 것이 특징이며 이를 활용하여 JS DOM API를 직접 사용하지 않고 DOM 요소를 다루기 위한 용도로 자주 사용된다.

 

1) 주요 특징

  • DOM 접근 : useRef를 사용하여 DOM 요소에 직접 접근할 수 있다. 
    • focus
    • scroll
    • animation

useRef hook으로 DOM에 접근하여 input 요소에 focus를 설정

 

  • 초기값 설정 : useRef는 초기값을 설정할 수 있으며, 그 값은 변경 가능하다.
  • 상태 유지 : 렌더링 사이에 값을 유지할 수 있지만, 이 값이 변경되어도 컴포넌트가 다시 렌더링되지 않는다.

상태 유지하기 예시 코드

 

예시에서 버튼을 클릭할 때마다 countRef의 값이 증가하지만, 컴포넌트는 리렌더링되지 않는 것을 알 수 있다. 이로 인해 이전 클릭 수를 유지하면서도 UI는 업데이트되지 않기 때문에 성능이 최적화된다고 할 수 있다.

 

→ useState는 리렌더링이 꼭 필요한 값을 다룰 때 사용하고 useRef는 리렌더링을 발생시키지 않는 값을 저장할 때 사용하자!


4. useContext

useContext는 Context API와 함께 사용하여 컴포넌트 트리에서 데이터를 효율적으로 공유할 수 있게 해준다. Context API는 상태를 전역적으로 관리하고, 중첩된 컴포넌트 간에 props를 일일이 전달하지 않고도 데이터를 전달할 수 있는 방법을 제공한다.

 

1) react context의 필요성

* prop drilling이란?

React에서 상위 컴포넌트가 하위 컴포넌트로 데이터를 전달할 대 발생하는 현상으로, 중간에 위치한 컴포넌트들이 데이터를 직접 사용하지 않음에도 불구하고 그 컴포넌트들을 통해 props가 전달되는 과정을 의미한다. 이로 인해 컴포넌트 트리가 깊어질수록 불필요한 props 전달이 발생하게 된다.

 

prop drilling의 문제점

  • 복잡성 증가
    • 중간 컴포넌트가 props를 전달해야 하므로, 코드가 복잡해지고 가독성이 떨어질 수 있다.
  • 유지보수 어려움
    • prop drilling 현상이 심한 경우, 나중에 컴포넌트를 수정하거나 재사용할 때 어려움이 발생할 수 있다. 어떤 컴포넌트가 어떤 props를 전달하는지 파악하기 어려워지기 때문이다.
  • 불필요한 렌더링
    • 중간 컴포넌트가 props를 변경하지 않더라도, props가 변경된 경우 해당 컴포넌트도 같이 리렌더링된다. 이는 성능에 영향을 미칠 수 있다.

 

이러한 prop drilling 현상을 해결하기 위해 react context API가 등장하였다. useContext hook을 통해 개발자는 이전보다 쉽게 전역 데이터를 관리할 수 있게 되었다.

 

2) context API 필수 개념

  • createContext : context를 생성한다.
  • useContext : context를 구독하고 해당 context의 현재 값을 읽는다.
  • Provider : context를 하위 컴포넌트에게 전달한다.

강의에서는 코드를 통하여 useContext가 왜 쓰여야하는지에 대해 설명하였다. 강의의 실습내용을 말로 풀어서 설명해보자.

 

  • useContext를 사용하지 않았을 때
    • 먼저 대가족이 있다. (할아버지 → 아버지 → 자식 → 손주)
    • 할아버지(최상위 컴포넌트)께서 손주(최하위 컴포넌트)에게 용돈(prop)을 전달하고자 할 때,
    • 할아버지(최상위 컴포넌트)가 아버지(상위 컴포넌트)에게 손주에게 줄 용돈(prop)을 전달해달라고 할 것이다.
    • 아버지(상위 컴포넌트)는 할아버지에게 받은 용돈(prop)을 자식(하위 컴포넌트)에게 주면서 손주에게 주라고 할 것이다.
    • 자식(하위 컴포넌트)은 아버지에게 받은 용돈(prop)을 손주(최하위 컴포넌트)에게 전달할 것이다.
  • useContext를 사용하였을 때
    • 할아버지(최상위 컴포넌트)께서 손주(최하위 컴포넌트)에게 용돈(prop)을 전달하고자 할 때,
    • 할아버지(최상위 컴포넌트)는 할머니(Context)에게 용돈(prop)을 맡겨놓고 손주(최하위 컴포넌트)에게 찾아가라고 한다.
    • 손주(최하위 컴포넌트)는 할머니께 찾아가 할아버지(최상위 컴포넌트)께서 맡겨놓은 용돈(prop)을 받는다.

이렇게 말로만 적어놓더라도 useContext를 사용하지 않으면 굉장히 번거로운 과정을 거쳐서 할아버지께서 손주에게 용돈을 전달할 수 있게 된다. 이처럼 Context API는 전역 상태 관리에 유용하며, 컴포넌트 간의 데이터 흐름을 간단하게 만들어준다.

 

3) 주의사항

useContext를 사용할 때에는 항상 리렌더링 이슈를 주의해야 한다. Provider에서 제공한 value가 달라진다면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링되기 때문이다. 따라서 value 부분을 항상 신경써주어야 한다. 이 때 중요한 개념이 메모이제이션이다. 메모이제이션은 내일 학습한 뒤 자세하게 정리하고자 한다.

 

* 메모이제이션(Memoization)이란?

컴퓨터에서 성능을 최적화하기 위해 사용하는 기법으로, 주로 함수의 결과를 캐시하여 동일한 입력에 대해 다시 계산하지 않고 바로 결과를 반환하는 방식이다. 이 방법은 특히 계산 비용이 높은 함수에서 유용하게 사용된다.


★ 20일차 소감

 

react 숙련 주차가 시작되었고, 동기부여 영상을 시청하여 발제가 시작되었다.. 동기영상으로 시작하는 발제.. 뭔가 앞으로는 지금보다 훨씬 더 많이 빡빡하게 굴러갈 것 같은 예감에 불안하다ㅋㅋ 이번 주차는 2주동안 진행되며 1주차에는 개인학습과 개인과제를 병행하고 2주차에는 팀프로젝트를 진행한다고 한다. 이번주는 저번주보다도 더 열심히 공부하고 배워서 두 과제를 모두 성공적으로 마칠 수 있도록 할 것이다. 오늘 학습한 hook은 정말 자주 사용된다고하니 절대 까먹지 않도록 틈틈히 복습을 해야겠다.