프론트엔드/실전 리액트 프로그래밍

[스터디 with 실전 리액트 프로그래밍] 11편 - 훅(Hook)

Junheehee 2022. 8. 16. 00:42

실전 리액트 프로그래밍

 

훅(Hook)

 

훅은 컴포넌트에 기능을 추가할 때 쓰는 함수이다.

상탯값(state), 생명 주기(life cycle)등에 관련한 직관적인 API를 제공한다.

 

 

 

Introducing Hooks – React

A JavaScript library for building user interfaces

reactjs.org

 

 

훅에 대한 공식문서다.

 

 

 

 

useState

 

가장 많이 사용되는 훅은 useState다.

useState를 사용하면 컴포넌트에서 상탯값을 추가하고 관리할 수 있다.

상탯값에 대한 설명은 아래 글에 나와 있다.

 

 

[스터디 with 실전 리액트 프로그래밍] 9편 - UI 데이터

3장은 리액트의 핵심 기능과 개념을 설명한다. 이 장의 도입부에서 프레임워크나 라이브러리를 온전히 이해하지 못한 채로 프로젝트를 진행하면 기술 부채가 늘어난다고 했다. 기술 부채, 내가

junhee-hee.tistory.com

 

 

 

useState가 반환하는 배열의 첫번째 원소는 상탯값, 두번째 원소는 상탯값 변경 함수다.

상탯값 변경 함수가 호출되면 해당 컴포넌트는 다시 렌더링된다.

이 때 자식 컴포넌트도 같이 렌더링된다.

 

상탯값 변경 함수는 비동기로 동작한다.

때문에 한번에 여러 개의 상탯값이 변경되었다고 해서, 여러 번 렌더링되지 않는다.

이는 성능 저하를 막아준다.

 

 

import React, { useState } from "react";

export function TestUseState1() {
  const [count, setCount] = useState(0);
  const add = () => {
    setCount(count + 1);
    setCount(count + 1);
  };
  console.log("Component is rendered");

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={add}>1 증가</button>
    </div>
  );
}

버튼을 클릭했을 떄 갱신 함수를 두번 호출하도록 했다.

 

setState는 비동기 작동

하지만 setState함수(상탯값 갱신 함수)는 비동기로 작동해서 렌더링은 한번만 되었다.

 

 

혹시 하나의 state를 여러번 변경하는게 아니라, 여러 state들을 한번에 변경하면 어떻게 될까 궁금해서 아래처럼 시도해봤다.

 

import React, { useState } from "react";

export function TestUseState1() {
  const [count, setCount] = useState(0);
  const [count1, setCount1] = useState(0);
  const add = () => {
    setCount(count + 1);
    setCount1(count1 + 1);
  };
  console.log("Component is rendered");

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={add}>1 증가</button>
    </div>
  );
}

역시 한번만 렌더링 되었다.

 

 

여기서 처음 코드를 보고 의아할 수 있다.

한번만 렌더링되는 건 이해하겠는데 count의 값은 2가 증가해야 되지 않나하고 말이다.

setState가 비동기로 작동해서 1만 증가한 것이다.

 

 

만약 상탯값 변경을 여러번 하고 싶다면 setState에 함수를 인수로 넣어줘야한다.

호출되기 직전의 상탯값을 매개변수로 받기 때문에 2가 증가한다.

 

import React, { useState } from "react";

export function TestUseState1() {
  const [count, setCount] = useState(0);
  const add = () => {
    setCount((count) => count + 1);
    setCount((count) => count + 1);
  };
  console.log("Component is rendered");

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={add}>1 증가</button>
    </div>
  );
}

setState 인수에 함수 사용

이 때도 렌더링은 한번만 일어난다.

 

 

상탯값으로 문자열, 숫자뿐만 아니라 배열, 객체도 관리할 수 있다.

 

 

 

 

useEffect

 

함수 실행 시 함수 외부의 상태를 변경하는 연산을 사이드 이펙트(부수 효과)라고 부른다.

웬만하면 사이드 이펙트는 useEffect로 처리하는 것이 좋다

 

useEffect를 사용할 때는 첫번째 인수로 함수를 넣어야 한다.

이 함수는 렌더링 결과가 실제 돔에 반영된 후 비동기로 호출된다.

 

 

import { useState, useEffect } from "react";

export function TestUseEffect1() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `count: ${count}`;
  });

  return <button onClick={() => setCount(count + 1)}>1 증가</button>;
}

 

 

useEffect 사용1

useEffect를 사용해 document의 제목을 업데이트 했다.

 

 

useEffect의 두번째 인수로는 배열을 넣는다.

이 배열을 의존성 배열이라 하고, 의존성 배열을 사용하면 렌더링될 때가 아니라 배열의 값이 변경될 때만 함수가 실행된다.

의존성 배열을 사용해도 최초 렌더링될 때는 실행된다.

만약 빈배열을 넣는다면 최초 렌더링될 때만 함수가 실행된다.

 

import { useState, useEffect } from "react";

export function TestUseEffect1() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `count: ${count}`;
  }, []);

  return <button onClick={() => setCount(count + 1)}>1 증가</button>;
}

의존성 배열이 비워져있을 때

useEffect의 함수가 최초 렌더링됐을 때만 호출되므로 버튼을 아무리 눌러도 document의 제목이 바뀌지 않는다.

 

 

useEffect는 컴포넌트가 렌더링될 때뿐만 아니라 사라질 때도 기능을 추가할 수 있다.

사이드 이펙트 함수에서 함수를 반환하면 된다.

이 반환된 함수는 사이드 이펙트가 호출되기 직전과 컴포넌트가 사라지기 직전에 마지막으로 호출된다.

 

 

import { useState, useEffect } from "react";

export function TestUseEffect1() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `count: ${count}`;
  }, []);

  return <button onClick={() => setCount(count + 1)}>1 증가</button>;
}

export function TestUseEffect2() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", onResize);
    return () => {
      window.removeEventListener("resize", onResize);
    };
  }, []);

  return <div>{`width is ${width}`}</div>;
}

useEffect - 반환함수

useEffect를 이용해 컴포넌트가 최초 렌더링되었을 때 window에 이벤트 리스너를 달았다.

이 컴포넌트가 사라질 때는 useEffect의 반환함수가 호출되어 window의 이벤트 리스너가 제거된다.

 

 

 

 

커스텀 훅

 

리액트 개발자는 목적에 맞게 커스텀 훅을 만들 수 있다.

위에서 창 너비를 구하는 로직을 커스텀 훅으로 만들어 관리해보자.

 

// 커스텀 훅

import { useState, useEffect } from "react";

export default function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", onResize);
    return () => {
      window.removeEventListener("resize", onResize);
    };
  }, []);
  return width;
}


// 커스텀 훅을 사용하는 컴포넌트
export function TestUseEffect3() {
  const width = useWindowWidth();

  return <div>{`width is ${width}`}</div>;
}

똑같이 잘 작동한다.

 

 

훅을 사용할 때 지켜야할 규칙이 있다.

 

  - 훅의 호출 순서는 항상 같아야 한다.

  - 훅은 함수형 컴포넌트 혹은 커스텀 훅 안에서만 호출되어야 한다.

 

왜냐하면 리액트 내부적으로 훅은 배열로 관리되기 때문이다.

만약 if문 등에 의해 훅의 순서가 달라진다면, 예상치 못한 오류가 발생할 수 있다.

 

 

 

 

코드

 

 

GitHub - junhee-won/react-study: with 실전 리액트 프로그래밍

with 실전 리액트 프로그래밍. Contribute to junhee-won/react-study development by creating an account on GitHub.

github.com