의존성 배열이란 뭘까 ?
짧게 얘기하자면 이렇다.
난 지금까진 이렇게까지만 알고 있었다.
근데 이 글을 적는다는건 문제가 있었다는거겠죠 .. ㅠ
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에서 버그가 발생하지 않게 의존성 배열을 잘 설정하는 방법을 적어보려고 한다.
말로만 들으면 엄청 쉽다. 외부 값을 사용했으면 의존성을 추가해주면 되는 일이니까.
하지만 ! 내가 ! 지금껏 못 넣은 이유는 !
의존성을 추가했더니 무한루프에 빠졌기 때문에 부득이하게 뺄 수 밖에 없었다..
왜였을까?
함수 컴포넌트의 내부에서 선언한 Object, Function의 경우에는 함수 컴포넌트의 매 호출마다 새로운 객체, 함수가 선언되고 참조형 데이터 타입의 특징으로 인해 객체 내부의 요소들이 동일하더라도 새롭게 생성된 객체와 이전 객체를 비교하면 서로 다른 객체라고 판단되게 된다.
그럼 무한루프 시작인거지 ㅎ
그래서 문제를 해결하기 위한 3가지 방안을 가져왔습니다 !
// 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
안에다가 선언하여 의존성에 추가하지 않게끔 만드는 것이다.
// 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");
};
컴포넌트 바깥에 함수를 두면 어떻게 될까 ?
한번 선언되고 값이 절대 바뀌지 않는다.
즉, 컴포넌트가 렌더링 되어도 값이 변하지 않기 때문에 의존성에 영향을 미치지 않는다.
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);
}, []);
이렇게되면 메모이제이션 비용이 늘어나지 않나요 ?
- 네, 맞습니다. 퍼포먼스적인 측면에서 효율을 따지자면 중복코드를 사용하는게 더 좋을수도 있지만
중복코드를 사용하면서 코드의 복잡성을 높이고 유지보수성을 낮추는 상황보단 낫습니다 !
그렇기에 최후의 수단으로만 메모이제이션을 사용하는 이유기도 합니다.
다 넣으라매.... 무슨 뜻일까 ?
예시를 보자면
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에서 직접적으로 사용하지 않고, 의존성에서 제거할 수 있다.
참고자료