useEffect ! 너 내 도도도독!!!

장택진·2023년 9월 19일
0
post-thumbnail

의존성 배열

의존성 배열이란 뭘까 ?
짧게 얘기하자면 이렇다.

  • useEffect에 두번째 인자로 넘기는 배열.
  • 두번째 인자를 넘기지 않으면 Effect는 매번 실행되고,
    빈 배열을 넘긴다면 컴포넌트의 첫번째 렌더링 이후에만 실행된다.

난 지금까진 이렇게까지만 알고 있었다.
근데 이 글을 적는다는건 문제가 있었다는거겠죠 .. ㅠ

useEffect(effect, 의존성)
여기서 effect는 함수의 형태로 표현되고,
의존성은 여러 의존성들을 한번에 전달하기 위해서 배열의 형태로 표현된다.

useEffect에서 의존성 배열이란 “무언가가 의존하고 있는 요소들의 모음” 이라고 할 수 있다. 그리고 여기서 말하는 무언가는 바로 effect 함수입니다.
즉, useEffect의 의존성 배열은 “effect 함수가 의존하고 있는 요소들의 모음” 이라고 할 수 있습니다.

“의존하고 있다” 라는 말이 어색하고 잘 이해가 안될 수도 있습니다. 이를 쉽게 풀어서 설명하자면 단순히 그냥 effect 함수가 사용하고 있는 외부의 값들이 의존성입니다.

function Component(){
	const [count, setCount] = useState(0);
	
	const effect = () => {
		document.title = `you clikced ${count} times`
	};

	useEffect(effect, [count]];
}

위의 예시에서 effect 함수는 count 라는 외부의 값을 내부에서 사용하고 있다. 따라서 effect 함수의 의존성은 count 이며 count를 의존성 배열에 넣어줘야하는 것이다.

이렇게 되면 useEffect는 리렌더링이 된 후 의존성 배열을 검사해서 의존성 배열에 있는 값들이 변경되었을 경우에 다시 새로운 의존성을 가지고 effect를 실행시켜 줍니다.

useEffect 의존성 배열을 잘 설정하는 법

대부분 필요없는 요소들을 굳이 의존성 배열에 넣는 실수는 잘 하지 않는다.
하지만, 필요한 의존성을 제대로 의존성 배열에 넣어주지 않는 실수를 많이 한다.

유경험자로서 그 이유를 빗대어 보자면

  • 넣었더니 에러가 나서
  • 뭘 넣으라는건지 몰라서

부끄럽지만 딱 이 두가지 이유로 지금껏 실수를 하고 있었다 !

지금부턴 useEffect에서 버그가 발생하지 않게 의존성 배열을 잘 설정하는 방법을 적어보려고 한다.

Step 1. 모든 의존성을 의존성 배열에 명시해라.

말로만 들으면 엄청 쉽다. 외부 값을 사용했으면 의존성을 추가해주면 되는 일이니까.
하지만 ! 내가 ! 지금껏 못 넣은 이유는 !
의존성을 추가했더니 무한루프에 빠졌기 때문에 부득이하게 뺄 수 밖에 없었다..

왜였을까?

함수 컴포넌트의 내부에서 선언한 Object, Function의 경우에는 함수 컴포넌트의 매 호출마다 새로운 객체, 함수가 선언되고 참조형 데이터 타입의 특징으로 인해 객체 내부의 요소들이 동일하더라도 새롭게 생성된 객체와 이전 객체를 비교하면 서로 다른 객체라고 판단되게 된다.
그럼 무한루프 시작인거지 ㅎ

그래서 문제를 해결하기 위한 3가지 방안을 가져왔습니다 !

1. 의존성을 제거하자 (함수를 effect 안에 선언하기)

  • 나쁜 예
// bad
function Component(){
	const [count, setCount] = useState(0);

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

	useEffect(increaseCount, [increaseCount]];
}
  • 좋은 예
// good
function Component(){
	const [count, setCount] = useState(0);

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

		increaseCount();
	}, []];
}

useEffect에서 활용해야 하는 함수를 effect 안에다가 선언하여 의존성에 추가하지 않게끔 만드는 것이다.

2. 함수를 컴포넌트 바깥으로 이동시키기

  • 나쁜 예
// bad
function Component() {
	const getUserAuth = () => {
		localStorage.getItem("ACCESS_TOKEN");
	};

	useEffect(() => {
		const token = getUserAuth();
		// login....
	}, []];
};
  • 좋은 예
// good
function Component() {

	useEffect(() => {
		const token = getUserAuth();
		// login....
	}, [getUserAuth]];

};

const getUserAuth = () => {
	localStorage.getItem("ACCESS_TOKEN");
};

컴포넌트 바깥에 함수를 두면 어떻게 될까 ?
한번 선언되고 값이 절대 바뀌지 않는다.
즉, 컴포넌트가 렌더링 되어도 값이 변하지 않기 때문에 의존성에 영향을 미치지 않는다.

3. 메모이제이션 (최후의 수단)

function Component(){
	const [count, setCount] = useState(0);

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

	useEffect(() => {
		// do something 1
		increaseCount();
	}, []];

	useEffect(() => {
		// do something 2
		increaseCount();
	}, []];
}

위 예시처럼 1개 이상의 effect를 사용해야 하는 상황에선 어떻게 해야할까?
함수를 안에 넣자니 중복코드가 되어버리고,
컴포넌트 밖으로 빼자니 다른 값들을 참조할 수가 없다.

이런 경우엔 메모이제이션을 사용해주는게 맞다.

	const increaseCount = useCallback(() => {
		setCount(prev => prev + 1);
	}, []);

이렇게되면 메모이제이션 비용이 늘어나지 않나요 ?

  • 네, 맞습니다. 퍼포먼스적인 측면에서 효율을 따지자면 중복코드를 사용하는게 더 좋을수도 있지만
    중복코드를 사용하면서 코드의 복잡성을 높이고 유지보수성을 낮추는 상황보단 낫습니다 !
    그렇기에 최후의 수단으로만 메모이제이션을 사용하는 이유기도 합니다.

Step 2. 가능하다면 의존성을 적게 만들어라

다 넣으라매.... 무슨 뜻일까 ?

예시를 보자면


  useEffect(() => {
    const intervalID = setInterval(() => {
      setCount(count + 1);
    }, 1000);

    return () => clearInterval(intervalID);
  }, [count, setCount]);

딱 보기엔 괜찮은 코드 같다. 사용한 외부 값들을 다 집어넣어줬으니..
하지만 "최소화 하라" 를 적용해보자


  useEffect(() => {
    const intervalID = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    return () => clearInterval(intervalID);
  }, [setCount]);

setState()의 전달인자를 함수로 만들어서 이전의 state를 인자로 받고 새로운 state를 반환하는 함수를 생성할 수 있다.
이렇게 한다면 count 를 useEffect에서 직접적으로 사용하지 않고, 의존성에서 제거할 수 있다.

참고자료

profile
필요한 것은 노력과 선택과 치킨

0개의 댓글