React Hooks

yhyem·2024년 3월 9일

useEffect

react에서 side effect를 수행할 수 있도록 해주는 Hook

Side Effect란?

리액트 컴포넌트의 외부에서 일어나는 작업들을 말한다.

ex) localStorage와 같은 브라우저API, 서버 API를 통한 요청 등

왜 Side Effect 실행을 별도로 구분할까?

Side Effect 중 서버에서 많은 양의 데이터를 받아오는 작업을 수행한다고 가정하자.

useEffect를 이용하지 않고 위의 코드를 실행한다고 했을 때,

리렌더링이 될 때마다 해당 작업을 수행해서 프로그램 성능을 저하시킬 것이다.

때문에 Side Effect가 필요할 때만 해당 작업을 수행할 수 있도록 구분하는 것이다.

Side Effect가 필요할 때 특정 작업을 수행하려면 컴포넌트의 생명 주기에 대해 알아야 한다.

컴포넌트 생명 주기

Mount

처음 컴포넌트가 화면에 나타날 때

Render

처음 컴포넌트가 나타날 때와 변경사항이 있을 때

컴포넌트가 리렌더링 될 때

  • props가 변경될 때
  • 부모 컴포넌트가 리렌더링될 때
  • state값이 변할 때

Unmount

컴포넌트가 화면에서 사라질 때

해당 컴포넌트에서 구독했던 API 등을 해제하는 뒷정리 함수를 실행한다.

useEffect 사용

기본 문법

useEffect(function, [deps]);
  • function : 특정한 순간에 실행하고자 하는 side effect 함수
  • deps : 의존성 배열
    • deps가 없을 경우는 렌더링 될 때마다 실행
    • 특정 state가 변경될 때마다 위 함수를 실행
    • 빈 배열일 경우 mount 됐을 때만 실행

컴포넌트 생명 주기 실습

import React, { useEffect, useState } from "react";

const App = () => {
  const [text, setText] = useState("");
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    console.log("Mount!");
  }, []);

  useEffect(() => {
    console.log("rerender!");
  });

  useEffect(() => {
    console.log("counter change!");
  }, [counter]);

  const handleCounter = () => {
    setCounter(counter + 1);
  };

  const handleText = (e) => {
    setText(e.target.value);
  };
  return (
    <>
      <div>
        <input onChange={handleText} value={text} />
      </div>
      <div>
        <h1>{counter}</h1>
        <button onClick={handleCounter}>+1</button>
      </div>
    </>
  );
};

export default App;

useState

react에서 상태 관리를 도와주는 Hook

왜 로컬 변수로 상태 관리를 하면 안 될까?

function App() {
  let count = 0;
  const handleClick = () => {
    count++;
  };
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

export default App;
  • 로컬 변수는 리렌더링 시 값이 유지되지 않는다.
  • 로컬 변수의 변화 값은 리렌더링을 야기하지 않는다.

useState의 역할

  • state값에 대해 리렌더링이 일어나더라도 값을 유지해야 한다.
  • 리렌더링을 야기해야 한다.
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>+1</button>
    </>
  );
};

export default App;

useState 사용

기본 문법

const [state, setState] = useState(initialState);
  • state : 현재 상태 값을 가지는 변수, 초기값은 initialState의 값
  • setState : state를 수정할 수 있는 함수

배열 비구조화 할당

위의 예시에서 useState는 아래와 같은 배열을 반환한다.

[S, Dispatch<SetStateAction<S>>]

위 배열의 값들을 변수에 할당하고 싶다고 했을 때
배열을 해체해서 그 값을 각각 변수에 담을 수 있게 하는 문법

setState

  1. 새로운 값으로 업데이트
setState(newState);
  1. 함수형 업데이트
setState((prev)=>prev+1)
  • 현재 state값을 전달 받고, 반환해줌으로써 값을 수정한다.

특징

  1. Batch Update

성능을 위해 업데이트 작업을 일괄적으로 처리해서 리렌더링 횟수를 최소화하는 기능

import React, { useEffect, useState } from "react";

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const [count3, setCount3] = useState(0);

  useEffect(() => {
    console.log("render!");
  }, [count1, count2, count3]);

  const handleClick = () => {
    setCount1(count1 + 1);
    setCount2(count2 + 1);
    setCount3(count3 + 1);
  };

  return (
    <>
      <h1>{count1}</h1>
      <h1>{count2}</h1>
      <h1>{count3}</h1>
      <button onClick={handleClick}>+1</button>
    </>
  );
};

export default App;

성능을 개선해주지만 새로운 값으로 수정하는 방식을 이용했을 때,

같은 state에 대해 동시에 여러 번 수정 작업을 했을 때 마지막 수정 작업만 처리된다.

함수형 업데이트로 위 문제를 해결할 수 있다.

import React, { useEffect, useState } from "react";

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

  useEffect(() => {
    console.log("render!");
  }, [count]);

  const handleClick = () => {
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>+1</button>
    </>
  );
};

export default App;
  1. setState의 비동기 처리

batch update를 하기 때문에 수정 작업이 동기적으로 처리될 수 없다.

→수정 작업 이후 수정된 state를 이용한 동기적인 작업에서 변경된 state값을 사용할 수 없다.

import React, { useEffect, useState } from "react";

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

  const handleClick = () => {
    setCount((prev) => prev + 1);
    alert(count);
  };

  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>+1</button>
    </>
  );
};

export default UseState;

useEffect의 의존성 배열로 위 문제를 해결할 수 있다.

import React, { useEffect, useState } from "react";

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

  useEffect(() => {
    alert(count);
  }, [count]);

  const handleClick = () => {
    setCount((prev) => prev + 1);
  };

  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleClick}>+1</button>
    </>
  );
};

export default UseState;

useContext

Context를 사용할 수 있게 도와주는 Hook

전역 상태 관리?

여러 컴포넌트에서 사용하는 값은 보통 props로 전달한다.

하지만 컴포넌트의 deps가 깊어지게 되면

사용하지도 않는 props를 오직 전달하기 위해서 받아오고 전달하는 경우가 있다. (통로가 된 것처럼)

이를 Props Drilling이라고 한다.

이런 문제를 해결하기 위해 전역적으로 값을 공유할 수 있도록 해주는 것이 Context이다.

useContext는 생성된 Context를 사용할 수 있게 도와주는 Hook이다.

Context 생성

const SomeContext = createcContext(defaultValue);

생성된 SomeContext는 지정된 범위 내에서 props로 따로 전달되지 않아도 바로 사용될 수 있다.

Context.Provider

생성된 Context를 어떤 범위 내에서 사용할지 Provider를 통해 지정할 수 있다.

<SomeContext.Provider value={someValue}>
    <ChildComponent/>
</SomeContext.Provider>

위처럼 Provider로 감싼 자식 컴포넌트에서는 SomeContext값을 이용할 수 있다.

즉 Context.Provider 컴포넌트는 Context 사용 가능한 범위를 본인의 자식 컴포넌트로 지정하는 것이다.

useContext

useContext는 지정된 범위 내에서 Context 객체를 가져옴으로써

값을 사용할 수 있도록 해주는 Hook인 것이다.

const contextValue=useContext(SomeContext);

위처럼 SomeContext를 전달해서 그 값을 사용할 수 있다.

0개의 댓글