eslint야 디펜던시 잔소리좀 그만해! - 2022.04.03
리액트를 사용하는 개발자라면 반드시 사용하는 eslint가 있습니다. 바로 다양한 훅의 디펜던시를 잘 작성했는지 확인하는 ExhaustiveDeps입니다. 하지만 이 규칙... 굉장히 피곤한데 정확히 어떤 상황일 때 잔소리를 하는지 문서가 빈약한데 리액트 공식문서엔 간단한 설명만 있고, 종속성을 가능하면 정확하게 기입하는 게 좋다!라고 설명되어 있습니다.
지금까지는 단순히 해당 규칙을 켜 놓은 상태로 잔소리를 들어가며 종속성 배열을 수정 하곤 했는데, 문득 판단하는 기준이 뭘까?라는 생각이 들어서 조사를 조금 해봤습니다. 구글링으로도 관련된 레퍼런스를 찾기 어려워서 직접 린트 규칙에 대한 코드를 살펴보며 정리했습니다.
관련 코드
https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
이제 리액트와 한발짝 더 가까워질 준비가 되었습니다.
🤖 대원칙
디펜던시를 확인하는 로직에는 대 원칙이 있습니다. 바로 effect 내부에서 안정적인 값(stable value)을 사용한다면 해당 값은 무시한다는 것입니다.
그렇다면 안정적이다 라는 말은 무엇일까요? 원시 값? 참조값?
리액트에서 안정적이다. 라는 말은 리액트의 렌더링 흐름에 영향을 받지 않는 값을 의미합니다. 예를 들면 import 해온 외부 라이브러리가 있겠습니다.
그렇다면 그 이외에 어떤 값들을 안정적이라고 판단하는지 알아보도록 하죠.
const로 지정된 (string || number || null)
린트 로직을 뒤져보다 보면 손쉽게 위와 같은 코드를 찾아볼 수 있습니다. 해당 조건을 만족하는지 몇 가지 코드를 작성해봤습니다.
Good 👍
Bad 👎
확인해보니 정말 그러네요!
undefined와 boolean 값에 대해선 왜 불안정하다고 판단하는지 별다른 설명이 적혀있지 않아서 해당 이유를 파악할 순 없었습니다. (문의중...)
어쨌든 우리는 하나의 stable 한 조건을 파악했습니다.
몇 가지 지정된 훅의 return value
해당 린트 규칙은 react 프로젝트에 완전히 의존적인 규칙이기 때문에 아주 대놓고 몇가지 훅의 리턴 값은 허용하고 있었습니다. 해당 값이 const로 지정된 리터럴 값이 아니라고 해도 말이죠.
리액트 렌더링 흐름상 세 가지 훅의 리턴 값은 안정적이다라고 판단하고 있었습니다. 저는 이 규칙을 보고 몇 가지 이상한 짓을 해봤는데요, 의도적으로 해당 값을 안정적이지 않게 바꾸는 것이었습니다.
Good 👍
?????
정말로 이상한 코드마저 안정적이다라고 판단하고 있었습니다. 훅의 리턴 값을 재 할당하는 일은 절대로 하면 안 될 행동이겠네요. 😅
어쨌든! 우리는 두 번째 안정적인 값이 무엇인지 알아봤습니다. 이제 마지막 안정적인 조건을 알아보도록 하죠.
캡처된 값이 없는 함수
디펜던시 린트는 내부의 모든 함수를 재귀적으로 확인합니다. 내부의 모든 함수 스코프 안에 모두 안정적인 값으로만 함수 로직이 작성되었는지 말이죠. 예시를 한번 보겠습니다.
Good 👍
Bad 👎
이상한 코드이긴 하지만 어쨌든 린트는 함수의 내부를 재귀적으로 탐사하며 불안정한 값이 있는지 확인합니다.
한 가지 헷갈려하지 말아야 할 점은 리액트 렌더링 흐름밖에 있는 값 + 함수는 항상 안정적이라고 판단한다는 것입니다.(good 예시 1번)
조금 다른 주제이긴 하지만 이와 같은 이유로 리액트에서의 상태 관리는 반드시 리액트 렌더링 흐름 안에 있어야 한다는 뜻도 됩니다. 벗어나는 순간 변화를 감지하지 않으니까요.
끝입니다! 🤩
안정적인 값 이외에 모든 값은 불안정하다고 판단합니다.
뭔가 더 복잡한 규칙이 있을 거 같지만 위에 설명한 규칙이 전부입니다! 어쨌든 린트 에러가 발생했다면 unstable 한 값이 effect 내부에 존재한다는 뜻이죠. 🤪
디펜던시 룰을 반드시 지켜야 하는가? 이것은 조금 다른 이야기이긴 하지만 어쨌든 어떤 이유 때문에 잔소리를 하는지 알고 있다면 더 안전하게 리액트를 사용할 수 있지 않을까요?
참고 자료
https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js