[React] React Hooks 의존성 배열에 객체가 존재한다면

Narcoker·2023년 6월 28일
0

React

목록 보기
10/32

의존성 배열을 사용하는 React Hooks의 동작 방식

useEffect, useCallback, useMemo
의존성 배열을 파라미터로 받는 React Hooks
의존성 배열의 모든 값들의 종속성(참조 값)을 비교해서
다르다면 첫번째 파라미터로 받은 함수를 동작시킨다고 한다.

실제로 Object.is() - MDN 메서드를 사용한다고한다.

=== 보다 엄격한 Object.is [JavaScript] Object Methods - Object.is()

원시 타입은 불변성을 가지고 있기 때문에 상관없다.

객체 타입heap 영역에 실제 값이 저장되어있고
call stack에 저장되어 있는 것은 실제 값의 참조 값이다.

그렇기 때문에 객체의 속성 값를 바꿔도 참조 값은 바뀌지 않기 때문에
Hook이 동작하지 않아야 한다.

즉, 새로운 객체가 식별자에 할당되어야만 Hook이 동작한다.

하지만 그렇지 않다.
의존성 배열에 객체를 넣으면 모든 행위에서 Hook이 동작한다고 한다.

왜 그런지 이유를 알고 싶었는데 모듈 동작 구조를 까보는데 실패했다..
[React] React Hooks 실제 선언 위치

예를 들어 객체의 속성을 변경해도 Hook이 동작한다.

번외 - useEffect

번외로 이 코드는 한 가지 문제점이 존재한다.

  • 클릭하세요 버튼을 클릭하면 count 값이 1 증가하는데
    useEffect의 의존성 배열에는 user 만 있음에도 실행된다.

개발자 모드 index.js 의 <React.StrictMode>에서는
초기 렌더링시에 useEffect가 두번 동작 한다.

배포 환경에서는 한번만 동작하니 넘어가길 바란다.
아니면 태그를 없애서 확인하길 바란다.

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

export default function App() {
  const [count, setCount] = useState(0);
  const user = { name: "Narcoker", age: 100 };

  const handleButton = () => {
    setCount((prevCount) => prevCount + 1);
  };

  useEffect(() => {
    console.log("참조 값이 변경되면 실행합니다.");
  }, [user]);

  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleButton}>클릭하세요</button>
    </>
  );
}

React의 컴포넌트는 State나 Props가 변경되면 즉시 재렌더링된다.
App() 함수가 처음부터 다시 실행된다는 의미이다.

즉, 변수의 값이 재생성 됨에 따라 참조값이 바뀌게 된다.
따라서 user 의 참조값이 바뀌기 때문에 useEffect가 실행이 되는 것이다.

참고로 hook들도 다시 선언된다.
하지만 내부적으로 상태값을 가지고 있으며
내부 메커니즘을 통해서 이 상태값은 유지가 된다.

해결 방안

Object.property

객체 자체가 아닌 변경될 객체의 속성의 식별자를 의존성 배열에 삽입한다.

useEffect(() => {
  console.log("참조 값이 변경되면 실행합니다.");
}, [user.name]);

JSON.stringify(Object)

객체를 JSON 문자열 값으로 변환하여 사용한다.

useEffect(() => {
  console.log("참조 값이 변경되면 실행합니다.");
}, [user.name]);

lodash 라이브러리의 ._isEqual(prev, next)

use-deep-compare-effect

Kent C.Dodds 가 개발한 라이브러리이다.
의존성 배열의 값들의 변경을 확인할때 깊은 비교를 한다.

사용 시 객체객체를 가지고 있는 배열만 의존성 배열에 추가하라고한다.

함수를 포함하는 객체는 의존성 배열에 넣으면 안된다고 한다.

의존성 배열에 추가하면 안되는 형태

const objectHasFunction = { func: () => {} };

useDeepCompareEffect(() => {
  console.log("함수를 가진 객체는 사용하지 말것");
}, [objectHasFunction]);

잘 동작하는 예제

import React, { useState } from "react";
import useDeepCompareEffect from "use-deep-compare-effect";

export default function App() {
  const [count, setCount] = useState(0);
  const user = { name: "Narcoker", age: 100, parent: { child: {} } };

  const handleButton = () => {
    setCount((prevCount) => prevCount + 1);
  };

  useDeepCompareEffect(() => {
    console.log("참조 값이 변경되면 실행합니다.");
  }, [user]);

  return (
    <>
      <h1>{count}</h1>
      <button onClick={handleButton}>클릭하세요</button>
    </>
  );
}

참조

profile
열정, 끈기, 집념의 Frontend Developer

0개의 댓글