본문 바로가기
Frontend/React

React 의존성 배열 🌱

by 우보틀 2022. 3. 27.
  • 리액트의 useCallback, useEffect등을 사용할 때 의존성 배열을 받게 됩니다. 이 배열의 역할은 무엇인가요?
  • 의존성 배열은 shallow equal, deep equal중 무엇을 하게 되나요?
  • 얕은 비교(Shallow Equal)와 깊은 비교에 대해서 설명해주세요.

 

의존성 배열의 역할은 배열안에 포함된 값의 변화를 감지하여 callback의 경우에는 함수의 재정의, effect의 경우에는 함수를 재실행 합니다.

이때 의존성 배열의 변화탐지는 shallow equal을 하게 됩니다. (리액트 에서는 javascript의 Object.is 비교 알고리즘을 사용합니다.)

얕은 비교는 해당 변수의 메모리 주소 값이 변경됬는지만 비교합니다. 

객체 일경우 key 값들의 세부 값까지는 비교하지 않습니다

깊은 비교는 비교 객체들의 최종 depth까지 값을 비교합니다.

 

  • useEffect는 기본적으로 렌더링 마다 실행됩니다.
  • useEffect의 의존성 배열에 빈 배열을 넣어주면 컴포넌트가 mount, unmount 될 때에만 실행됩니다.
  • useEffect의 primitive type의 값을 넣어주면 값이 변경될 때마다 실행됩니다.
  • useEffect의 의존성 배열에 reference type의 값을 넣어주면 object의 reference가 변경될때마다 실행됩니다.
  • 의존성 배열에 reference type을 넣어주고 내부 값들까지 변경될때마다 실행시켜주길 원한다면 useDeepCompareEffect와 같은 라이브러리의 사용을 검토할 수 있습니다.

아래 코드는 react에서의 dependency array를 비교하는 코드이다

for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
  if(is(nextDeps[i], prevDeps[i])) {
    continue;
  }
  return false;
}
return true;

서로 다르면 false를 반환하고 콜백이 실행되는 것으로 이해하였다.

prevDeps와 nextDeps중 길이가 짧은 것을 기준으로 한다.

그러므로 Object에 키값이 하나 추가되면 callback 함수가 실행되지 않는다.(어차피 주소가 같기 때문에 실행되지 않을 것이다.)

 

 

아래는 Object.is 를 사용한 예시 입니다.

Object.is('a', 'a') // true


const a = { b: 'b' }
Object.is(a, a) // true
Object.is(a, {...a}) // false
Object.is(a, Object.defineProperty(a, 'c', {value: 'c'})) // true

const b = a 
Object.is(a, b) // true

 

얕은 비교는 Object.is 나 === 비교 연산자를 통해 비교를 할 수 있다.

깊은 비교는 커스텀 함수를 사용하거나 외부 라이브러리를 이용할 것 같다.

아래는 직접 작성해본 커스텀 함수 이다.

// 깊은 비교
const deepEqual = (a, b) => {
  for (let key in a) {
    if (typeof a[key] === 'object') { 
      if (typeof b[key] === "object") {
        return deepEqual(a[key], b[key]);
      } else {
        return false;
      }
    }
    
    if (a[key] !== b[key]) {
      return false;
    }
  }

  return true
}

// 깊은 복사
const deepCopy = (a, temp) => {
  for (let key in a) {
    if (typeof a[key] === "object") {
      deepCopy(a[key], temp[key] = {})
    } else {
      temp[key] = a[key]
    }
  }

  return temp;
};

const a = { b: { c: 'c' }, d: { e: 'e' }}
const b = { b: { c: 'c' }}
const c = deepCopy(a, {})
const d = JSON.parse(JSON.stringify(c))

console.log(a) // { b: { c: 'c' }, d: { e: 'e' } }
console.log(b) // { b: { c: 'c' } }
console.log(c) // { b: { c: 'c' }, d: { e: 'e' } }
console.log(d) // { b: { c: 'c' }, d: { e: 'e' } }
console.log(Object.is(a, b)); // false
console.log(Object.is(a, c)); // false
console.log(deepEqual(a, b)); // true
console.log(deepEqual(a, c)); // true

 

깊은 복사를 할때는 JSON.parse, JSON.strinigfy를 사용한다.


 

의존성 배열이 너무 자주 변경되는 경우!

setState 함수에서 이전 값을 참조해서 가져오게 하면 너무 자주 변경되는 경우를 대비할 수 있다

 

import { useState, useEffect } from 'react';

const CounterTest = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(id);
  }, [])

  return <h1>{count}</h1>
}

export default CounterTest;

위와 같은 코드가 있습니다.

setInterval를 통해 1초마다 count가 +1 되길 기대할 수도 있지만 기대 했던대로 동작하지 않습니다.

 

effect 콜백이 실행되었을 때와 마찬가지로 count 값이 0으로 설정된 클로저를 생성하기 때문입니다.

매초 setCount(0 + 1)이 호출됩니다.

클린업 함수를 통해 setInterval은 한번만 실행될 것입니다.

 

 

import { useState, useEffect } from 'react';

const CounterTest = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1)
    }, 1000)
    return () => clearInterval(id);
  }, [count])

  return <h1>{count}</h1>
}

export default CounterTest;

위와 같이 useEffect의 의존성 배열에 count 값을 주어 count 값이 변경될 때마다 count에 +1 을 해줄수도 있습니다.

하지만 이렇게 되면 setInterval이 매번 새로 설정됩니다.

실제로 useEffect내부에 console을 찍어보면 계속 출력할 것입니다.

 

import { useState, useEffect } from 'react';

const CounterTest = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const id = setInterval(() => {
      setCount(num => num + 1)
    }, 1000)
    return () => clearInterval(id);
  }, [])

  return <h1>{count}</h1>
}

export default CounterTest;

위와 같이 작성하면 setInterval이 매번 초기화되지 않으면서 1초마다 count에 +1을 해줄수 있습니다.

이렇게 이전 state를 사용해서 새로운 state를 계산하는 것을 함수적 갱신이라 합니다

이를 이용하면 현재 state를 참조하지 않고 state를 변경해야 하는 방법을 지정할 수 있습니다.

 

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is

 

Object.is() - JavaScript | MDN

Object.is() 메서드는 두 값이 같은 값인지 결정합니다.

developer.mozilla.org

https://ko.reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

 

Hook 자주 묻는 질문 – React

A JavaScript library for building user interfaces

ko.reactjs.org

https://ko.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often

 

Hook 자주 묻는 질문 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

'Frontend > React' 카테고리의 다른 글

React.memo 🌱  (0) 2022.03.27
React useCallback, useMemo  (0) 2022.03.27
React 피드백 내용 정리  (0) 2022.03.04
React useLayoutEffect  (0) 2022.03.03
제어 컴포넌트 vs 비제어 컴포넌트  (0) 2022.03.03