ECMAScript | TypeScript

React의 hook deps와 Object.is

partner_jun 2023. 9. 17. 12:14

의문

리액트의 훅은 deps값을 저장하고, 이전 값과 비교하여 변화가 있을 때 재실행(갱신)하도록 한다.

deps 비교는 당연히 reference 비교라고 생각했기에 아무런 의심 없이 사용해왔다. 하지만 최근 IDE의 자동 완성을 이용해 deps 구문을 작성했을 때 의도했던 갱신이 이루어지지 않았다. 확인해 보니 deps에 등록된 객체의 필드가 문자열이었고, 객체가 변경되었지만 문자열 자체는 변하지 않았기 때문이었다. 이 참에 deps에 대해 짚어보자는 생각으로 리액트의 코드를 확인해 보고, 그 기록을 남긴다.

 

물론 이 글 역시 너무 오랫동안 글을 쓰지 않아 팀 메신저에 공유했었던 내용을 정리하여 다시 쓰는 글이다...

 

 

React는 훅의 deps를 어떻게 비교하는가?

먼저 리액트의 코드를 직접 확인해 본다. 확인한 코드 플로우는 아래와 같다.

 

- ReactFiberHook.js:2329 (function updateEffectImpl)

- ReactFiberHook.js:445 (function areHookInputsEqual)

- objectIs.js (function ObjectIs)

 

 

코드를 통해 두가지를 알 수 있다.

 

 

1. deps에 아무런 값도 넣지 않으면 렌더링 될 때마다 재실행되는 이유

생각보다 굉장히 어이없지만 그냥 for 구문이라 그렇다.

훅 deps의 상태값을 비교하는 함수인 areHookInputsEqual에서 값이 같지 않을 때 false를 리턴하는데, 배열이 아니면 그냥 이 구문이 실행되지 않기 때문이다. 그렇다면 length만 있는 객체를 넣으면?

deps에 배열이 아닌 값을 넣었다는 경고가 노출되기는 하지만 의도한(?) 대로 값이 변해도 useEffect 구문이 실행되지 않는다.

 

 

2. deps 배열 내의 값을 비교하는 방법

Object.is를 호출해 deps 배열 내의 값을 비교하되, 없는 경우(라고는 하지만 리액트가 지원하는 브라우저보다 버전이 낮은 브라우저에서 구현되는 함수다) 폴리필로 구현된 is 함수를 호출하는 것이다. MDN의 설명에 따르면 Object.is 함수는 === (eq)과도 다르다고 한다.

 

직접 테스트해보자.

확실히 set plus 버튼과 set minus 버튼을 번갈아 클릭했을 때 useEffect 구문이 실행되는 것을 확인 할 수 있었다. 

 

3. hook을 한번만 실행하는 방법

흔히 빈 배열을 넣음으로써 훅이 한번 실행되도록 한다. 하지만 위 코드에서 볼 수 있듯 memorizedState.preDeps가 null인 경우는 무조건 훅 함수가 갱신되기 때문에 컴포넌트가 렌더링 될 때마다 함께 갱신된다.

대부분의 경우는 이것으로도 충분하지만 상황에 따라 어플리케이션 수준의 한번만 코드가 실행되어야 하는 경우도 있다. 이러한 경우는 컴포넌트를 다시 렌더링시키지 않는 참조값인 Ref를 이용해 실행 여부를 주입해주는 방식으로 간단하게 구현할 수 있다.

훅 함수 자체가 갱신 되는 것을 막을 수는 없지만 이미 실행했는지 여부를 상위 컴포넌트에서 관리하여 실행을 제어하는 것이다. 아주 간단한 코드이지만 생각보다 유용하게 사용할 수 있다.

 

 

이미 알고있던 내용을 코드를 통해 되짚어보았다. 사실 이런 것들은 '왜 이런가?' -> '그냥 그렇게 만들어져서'에 가까운 내용들이다. 코드를 작성한 당사자들이 논의를 해서 결정한 것인지, 독단적으로 작성했지만 이견이 제기되지 않고 그대로 넘어갔는지는 그들만이 알 뿐이다. 하지만 늘 그렇듯 이런 코드를 살펴보면 잊었던 것을 되짚고 '나라면 어떻게 구현했을까', '이런 상황에서는 어떻게 쓸까'를 고민하는 기회가 된다.