리액트 훅 깊게 살펴보기 (3)

리액트 훅 깊게 살펴보기 (3)

모던 리액트 Deep Dive: 리액트의 핵심 개념과 동작 원리부터 Next.js까지, 리액트의 모든 것 - 03장 리액트 훅 깊게 살펴보기 (3)

1. 리액트의 모든 훅 파헤하기

useLayoutEffect

useLayoutEffectuseEffect와 비슷하지만, 모든 DOM의 변경 후에 동기적으로 발생한다.

function App(props) {
	 const [count, setCount] = useState(0)
	
	 useEffect(() => {
		console.log("useEffect", count)
	}, [count])
	
	useLayoutEffect(() => {
		console.log("useLayoutEffect", count)
	}, [count])
	
	function handleClick() {
	setCount((prev) => prev + 1)
	}
	
	return (
	<>
		<h1>{count}</h1>
		<button onClick={handleClick}>+</button>
	</>
	);
}

useLayoutEffect는 ‘모든 DOM의 변경 후에 useLayoutEffect의 콜백 함수 실행이 동기적으로 발생’한다. (DOM 변경이란 렌더링이다.)

  1. 리액트가 DOM을 업데이트
  2. useLayoutEffect를 실행
  3. 브라우저에 변경 사항을 반영
  4. useEffect를 실행

useLayoutEffect가 브라우저에 변경 사항이 반영되기 전에 실행되고 useEffect는 브라우저에 변경 사항이 반영된 이후에 실행된다.

리액트 컴포넌트는 useLayoutEffect가 완료될 때까지 기다리기 때문에 컴포넌트가 잠시 동안 일시 중지되는 것과 같은 일이 발생하게 된다.

useLayoutEffect의 특성상 DOM은 계산됐지만 이것이 화면에 반영되기 전에 하고 싶은 작업이 있을 때와 같이 반드시 필요할 때만 사용하는 것이 좋다. DOM 요소를 기반으로 한 애니메이션, 스크롤 위치를 제어하는 등 화면에서 사용하면 좋다.

useDebugValue

useDebugValue는 리액트 애플리케이션을 개발하는 과정에서 사용되며, 디버깅하고 싶은 정보를 이 훅에다 사용하면 리액트 개발자 도구에서 볼 수 있다.

function useDate() {
	const date = new Date()
	useDebugValue(date, (date) => `현재 시간: ${date.toISOString()}`)
	return date
}

export default function App() {
	const date = useDate()
	const [counter, setCounter] = useState(0) 
	
	function handleClick() {
		setCounter((prev) => prev + 1)
	}
	
	return (
		<div className="App">
			<h1>
				{counter} {date.toISOString()}
			</h1>
			<button onClick={handleClick}>+</button>
		</div>
	)
}

useDebugValue는 사용자 정의 훅 내부의 내용에 대한 정보를 남길 수 있는 훅이다. 두 번재 인수로 포매팅 함수를 전달하면 이에 대한 값이 변경됐을 때만 호출되어 포매팅된 값을 노출한다. (첫 번째 인수의 값이 같으면 포매팅 함수는 호출되지 않는다.)

useDebugValue를 사용할 때는 오직 다른 내부에서만 실행할 수 있다.

훅의 규칙

리액트에서 제공하는 훅은 사용하는데 몇 가지 규칙이 존재하는데 이것을 rules-of-hook라고 하며 이와 관련된 ESLint 규칙인 react-hooks/reules-of-hooks이 있다.

  1. 최상위에서만 훅을 호출해야 한다. 반복문이나 조건문, 중첩된 함수 내에서 훅을 실행할 수 없다.

  2. 훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 훅은 사용자 정의 훅의 두 가지 경우뿐이다.

리액트 훅은 파이버 객체의 링크드 리스트의 호출 순서에 따라 저장된다. 각 훅이 파이버 객체 내에서 순서에 의존해 stateeffect의 결과에 대한 값을 저장하고 있기 때문이다.


사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?

사용자 정의 훅

서로 다른 컴포넌트 내부에서 같은 로직을 공유하고자 할 때 주로 사용되는 것이 바로 사용자 정의 훅이다.

사용자 정의 훅은 리액트에서만 사용할 수 있다.

이 사용자 훅의 규칙 중 하나는 이름이 반드시 use로 시작하는 함수를 만들어야 한다는 것이다. 해당 함수가 리액트 훅이라는 것을 바로 인식할 수 있다.

fetch를 기반으로 HTTP 요청을 하는 사용자 정의 훅을 한번 살펴보자.

import "./App.css";
import { useEffect, useState } from "react";

function useFetch<T>(
  url: string,
  { method, body }: { method: string; body?: XMLHttpRequestBodyInit }
) {
  // 응답 결과
  const [result, setResult] = useState<T | undefined>();
  // 요청 중 여부
  const [isLoading, setIsLoading] = useState<boolean>(false);
  // 2xx, 3xx 로 정상 응답인지 여부
  const [ok, setOk] = useState<boolean | undefined>();
  // HTTP setStatus
  const [status, setStatus] = useState<number | undefined>();

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    (async function fetchData() {
      setIsLoading(true);
      try {
        const response = await fetch(url, {
          method,
          signal,
          body,
        });
        const data = await response.json();
        setResult(data);
        setOk(response.ok);
        setStatus(response.status);
      } catch (error) {
        setResult(undefined);
        setOk(false);
        setStatus(undefined);
      } finally {
        setIsLoading(false);
      }
    })();

    return () => {
      controller.abort();
    };
  }, [url, method, body]);
  return { ok, result, isLoading, status };
}

interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

export default function App() {
  const { isLoading, result, status, ok } = useFetch<Array<Todo>>(
    "https://jsonplaceholder.typicode.com/todos",
    { method: "GET" }
  );

  useEffect(() => {
    if (!isLoading) {
      console.log("fetchResult >>", status);
    }
  }, [status, isLoading]);

  return (
    <div>
      {ok
        ? (result || []).map(({ userId, title }, index) => (
            <div key={index}>
              <p>{userId}</p>
              <p>{title}</p>
            </div>
          ))
        : null}
    </div>
  );
}

만약 훅으로 분리하지 않았다면 fetch로 API 호출을 해야 하는 모든 컴포넌트 내에서 공통적으로 관리되지 않는 최소 4개의 state를 선언해서 각각 구현했어야 할 것이다.

사용자 정의 훅은 내부에 useStateuseEffect 등을 가지고 자신만의 원하는 훅을 만드는 기법이며, 내부에서 리액트 훅을 사용하고 있기 때문에 당연히 앞서 언급한 리액트 훅의 규칙을 따라야 한다. (use사용)

고차 컴포넌트

고차 컴포넌트(HOC, Higher Order Component)는 컴포넌트 자체의 로직을 재사용하기 위한 방법이다.

고차 컴포넌트는 고차 함수(Higher Order Function)의 일종이다. (자바스크립트에서도 사용 가능)

리액트에서 가장 유명한 고차 컴포넌트는 리액트에서 제공하는 API 중 하나인 React.memo다.

React.memo란?

props의 변화가 없음에도 컴포넌트의 렌더링을 방지하기 위해 만들어진 고차 컴포넌트다.

props를 비교해 이전과 props가 같다면 렌더링 자체를 생략하고 이전에 기억해 (memoization) 컴포넌트를 반환한다.

useMemo를 사용해서도 동일하게 메모이제이션할 수 있다.

function Component() {
  const MemoComponent = memo(ChildComponent);
  const MemoizedChildComponent = useMemo(() => {
    return <ChildComponent />;
  }, []);
  return (
    <div>
      {MemoizedChildComponent}
      <MemoComponent />
    </div>
  );
}

고차 함수 만들기

고차 함수는 ‘함수를 인수로 받거나 결과로 반환하는 함수’라고 정의돼 있다. 고차 함수로는 Array.prototype.map 등이 있다.

const list = [1, 2, 3]
const doubleList = list.map((item) => item * 2)

고차 함수를 활용하면 함수를 인수로 받거나 새로운 함수를 반환해 완전히 새로운 결과를 만들어 낼 수 있다. 또한 함수를 생성할때 선언한 환경 클로저에 변수들이 기억되어, 이 원리를 이용해서 다양한 작업을 해볼 수 있다.

function add(a) {
	return function (b) {
		return a + b
	}
}

const result = add(1) // 여기서 result는 앞서 반환한 함수를 가리킨다.
const result2 = result(2) // 비로소 a와 b를 더한 3이 반환된다.

고차함수를 활용해 리액트 고차 컴포넌트 만들어보기

고차 컴포넌트를 사용할 때 주의할 점이 있다.

  • 고차 컴포넌트를 구현할 때는 with로 시작하는 이름을 사용해야 한다. (관습이라 보면 된다.)
  • 부수 효과를 최소화 해야 한다.
    • 고차 컴포넌트는 반드시 컴포넌트를 인수로 받게 되는데, 반드시 컴포넌트의 props를 임의로 수정, 추가, 삭제하는 일은 없어야 한다.
// Define the Higher-Order Component
function withLogging(WrappedComponent) {
  // Return a functional component
  return function WithLoggingComponent(props) {
    // Log props when component mounts
    useEffect(() => {
      console.log("Component props:", props);
    }, [props]);

    // Render the wrapped component with its props
    return <WrappedComponent {...props} />;
  };
}

사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?

사용자 정의 훅이 필요한 경우

단순히 useEffect, useState와 같이 리액트에서 제공하는 훅으로만 공통 로직을 격리할 수 있다면 사용자 정의 훅을 사용하는 것이 좋다. 사용자 정의 훅은 그 자체로는 렌더링에 영향을 미치지 못하기 때문에 사용이 제한이다. 따라서 컴포넌트 내부에 미치는 영향을 최소화해 훅을 원하는 방향으로만 사용할 수 있다는 장점이 있다.

단순히 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶다면 사용자 정의 훅을 사용하는 것이 좋다.

고차 컴포넌트를 사용해야 하는 경우

함수형 컴포넌트의 반환값 즉, 렌더링의 결과물에도 영향을 미치는 공통 로직이라면 고차 컴포넌트를 사용하면 좋다.

고차 컴포넌트가 많아질수록 복잡성이 기하급수적으로 증가흐므로 신중하게 사용해야 한다.


© 2021. All rights reserved.

Powered by Hydejack v9.1.6