react의 dependency에 관한 개곶통의 기록

해당 내용은 개인과제 회고록에도 있는 내용이지만 워낙 고생한 터라 나중에 찾아보기 위해서라도 따로 빼놓는 것도 있고, 필자와 같은 고통을 하지 말기를 바라며 누군가 보는 사람에게 도움이 되기를 원하는 마음으로 재작성하는 글

🖲 리엑트 쓰면서 dependency 함부로 무시하지 마라.

react 에서 useEffect를 사용하다보면 자주 사용하게 되는 두번째 인자 dependency...

useEffect(function, [])

나는 여지껏 저 dependency를 그냥 첫째 인자에 저장되어 있는 함수를 비동기적으로 호출할 때 호출되는 기준으로 dependency 배열 내의 값의 변동을 체크한다, 라고만 생각하고 있었다.

그런데, 그 생각이 이번에 와장창 깨지면서 이것저것 곤란한 상황을 겪어 기록으로 남기게 된다.

물론, 위에 기술한 저 의미는 그 있는 그대로의 의미가 맞다.

하지만 그것보다 더 중요한 개념이 하나 더 있었다. 그것은 바로

첫번째 함수객체가 호출되어 실행이 될 때, 변수들을 최적화할지 말지에 대한 기준점이 된다는 것이다.

이게 무슨 의미인지 사진을 통해서 조금 더 설명하고자 한다.

해당 useEffect는 loading이라는 dependency 배열 내용이 바뀜으로 인해 첫째 인자인 콜백이 호출되는 상황이다.

근데, dependency 배열 내에는 loading만 존재하고 있고, 콜백 함수의 내부에서는 searchList라고 하는 변수를 참조해서 무언가를 하고 있다.

참고로, redux storage는 페이지 접근 순간 searchList가 업데이트가 되는 구조로 되어 있고 확인결과 데이터가 존재하는 것도 알 수 있었다.

그럼 저 결과대로 컴포넌트를 보여주는 UI 로직이 제대로 작동할까?

nope! 전혀 의도한대로 작동을 하지 않는것을 알 수 있다.

도대체 왜 그럴까? 계속 고민하고 시도해본 결과, react에서 dependency 배열이라는 것은 단순하게 첫번째 인자인 콜백을 비동기적으로 호출할지 말지에 대한 기준점이 되는것을 넘어서서, 내부 변수들을 새롭게 마운트된 데이터들을 기준으로 할지 말지에대한 기준점 역시 하고있다는 놀라운 사실을 알게된 것이다.

만약 dependency 배열에 필요한 새로운 데이터의 기준점을 추가해줄 경우 어떻게 될까.

이제서야 원하는 대로 결과가 나오는 것을 확인할 수 있다.
이것은 다른 최적화 관련 hook들도 마찬가지이다.

위의 useEffect는 target이 변동되면 호출되어 어떤 함수를 호출하도록 설계해두었다.

해당 함수는 useCallback으로 최적화하여 캐싱되어 있는 함수객체인데, 이때 호출을 하면 target을 콘솔로 찍도록 설계되어 있다.

그러나, 이때 dependecy 배열에는 target이 존재하지 않는다. 즉 target의 변동에 대해서 내부 콜백함수의 변수가 최적화되지 않는다는 소리다. 그러면 어떤 결과를 가져올까

아무리 store의 target이 변동되고 있더라도 콘솔로 target이 변동되지 않고 계속해서 캐싱된 렉시컬 환경의 target이 호출된다.

그럼 우리는 예상하건데 target을 여기에 넣는다면 내부의 변수가 최적화가 완료되어 항상 새로운 데이터를 기준으로 할 것을 알 수 있다.

dependency의 파워가 느껴지십니까?

나는 이 결과를 보고 너무 큰 충격을 받았고, react를 알면 알수록 끝이 없다는 사실을 경험하게 되었다. 도대체 왜 react는 자꾸만 dependency 배열을 다 안채워두면 뭐라고 화를내는걸까? 이제는 이 경험을 통해 명확하게 알게 되었다.

번외편으로, 그럼 dependency만 잘 설정하면 확실한걸까?

라고 위에처럼 생각하면 정말 안일하다는 것을 방금 전에 개발하다가 후려맞으면서 알았다.

다시금 말하지만 useEffect는 비동기 함수이다. 즉, callback을 호출할 때가 정해지지 않았다는 의미이다.

dependency 배열이 최적화를 보장해주는 것은 "동기적" 업데이트의 최신 결과를 의미하지 "비동기적" 업데이트의 결과까지 다 보장해주며 기다리는 것이 아니다.

사실상 비동기적으로 처리되는 내용이 useEffect로 이것 하나만 있었다면 이렇게까지 큰 문제는 발생하지 않는다.

그러나, 가장 큰 문제는 useEffect들끼리 서로 연결되어서 상태업데이트를 하며 영향을 받을 때이다.

물론 이딴 로직을 안짜는 게 제일 최우선되야겠지만 리펙토링으로 일반화한다고 설치다가 온갖 난리부르스를 겪게 되었다.

useEffect 콜백은 비동기적으로 호출되긴 하는데, 서로 task queue에 들어가기 위해 준비되는 시기가 서로 다르기 때문에 콜스텍으로 들어가는 순서도 정해지지 않고 서로 다르다.

그래서, 만약 한 useEffect가 실행하는 상태변경이 다른 useEffect가 크게 영향을 받을 경우, 이것이 제대로 반영되지가 않는다.

위의 내용은 useEffect의 콜백함수 구조이다. 여기서 보면 조건부로 sliderList라는 배열의 길이가 존재하지 않을 경우 무언가를 한다고 설계되어 있다.

근데 문제는 sliderList라는 애는 searchList라는 최초의 검색 리스트가 존재 해야만 거기서 결과를 뽑아낼 수 있는 구조로 되어있는데, 이 searchList 역시 useEffect로 비동기적으로 업데이트된다는 점이다.

그래서, 해당 내용을 호출해보면 무슨 상황이 벌어지게되냐면

응 아무것도 안보여줄거야 돌아가

이런 슬픈 결과를 마주하게 된다.

따라서 만약에라도 한 useEffect의 실행이 다른 useEffect의 비동기적 실행결과에 따른 상태업데이트에 영향을 받게 된다면 이것을 반드시 콜백 함수 내에서 조건부로 명시해줘야만 한다.

위에서도 볼 수 있듯, if문 내에서 명시적으로 searchList가 존재를 할 때에만 아래의 구문이 실행되도록 설정을 해두니 이제는 원하는대로 결과를 보여주는 것을 알 수 있었다.

결론

진짜 react 너무어렵다.

좋은 웹페이지 즐겨찾기