마운트, 언마운트, 특정 state 변경, 뷰 렌더시 필요한 로직을 호출
리액트의 컴포넌트는 함수형 혹은 클래스 형으로 작성할 수 있다. 컴포넌트 내에서 사용하는 여러 변수의 상태 업데이트와 생명주기를 관리하기 위해, 함수형 컴포넌트에서는 훅(Hook) 함수를 활용하고 클래스형 컴포넌트에서는 라이프사이클 함수를 활용한다. 내 생각에는 클래스형컴포넌트의 함수들이 이름이나 호출방법이 더 직관적인 것 같다. 함수형 컴포넌트의 훅은 사용하기에는 훨씬 간단하지만, 호출할 때 고려해야할 요소들이 좀더 많은 것 같다.
클래스형 컴포넌트가 마운트될 때 동작해야 하는 로직은 componentDidMount() 에서 호출하면 된다. 언마운트되기 직전에 동작해야 하는 로직은 componentWillUnmount() 에서 호출하면 된다.
함수형 컴포넌트에서는 useEffect() 훅을 이용하되, 주시하는 의존성 state 배열 자리에 빈 배열을 넣어주면 메인 로직은 최초 마운트 시에만 실행되고, return 하는 함수에 넣어주는 로직은 언마운트 시에만 실행된다.
다만 한가지 의문인 부분은, componentWillUnmount()의 내부 로직은, 소속된 컴포넌트가 언마운트되기 직전에 실행된다는 것이 확실한데 비해 useEffect() 의 return 함수 내부 로직은 소속된 컴포넌트의 언마운트와 순서관계를 잘 모르겠다는 점이다. 내 예상에는 컴포넌트 언마운트가 먼저 되고, 리턴값으로 받은 함수 로직이 그 직후에 실행되는 것 같다. 공식 문서에 나와있는 호출방법인 만큼 오동작할 우려는 적지만, 나의 사고방식에서는 컴포넌트에서 사용한 변수들을 초기화하는 로직을 해당 컴포넌트 언마운트보다 먼저 실행시키는 게 논리적으로 올바른 것 같아서 찝찝함이 남아 이 부분이 정확히 어떻게 처리되고 왜 문제가 없는 것인지 추후 좀더 찾아보려고 한다.
https://gist.github.com/yeonwooz/5295084e350cbb549e51091a9be62b67
클래스형 컴포넌트가 업데이트될 때 동작해야 하는 로직은 componentDidUpdate() 에서 호출하면 된다. componentDidUpdate는 컴포넌트에 업데이트가 있을 때마다 동작하며, 조건문을 이용해 특정 state 의 변경여부에 따른 로직을 구현한다.
함수형 컴포넌트에서는 매 렌더링시마다 실행시킬 로직은 의존성 배열 없이 구현하고, 특정 state 변경시마다 실행시킬 로직은 해당 state를 의존성 배열에 넣어 주시시켜서 구현한다.
https://gist.github.com/yeonwooz/9f67428ea3d9497d3479b99355acd78f
함수가 함수 그 자체 (function), 혹은 실행된 함수( function() )를 리턴하는 것은 둘다 가능하다.
https://gist.github.com/yeonwooz/3a99e5f3120bf5e095ddaf26c2018aa5
함수 a 를 선언한 후 함수 b가 a를 리턴하게 한 후 b 를 실행하면, a라는 함수로 정의된 객체자체가 리턴된다. 주의할 점은, 화살표 함수가 아닌 function() {} 선언문으 정의한 함수를 화살표 함수의 실행부에 넣어서 리턴하려고 하면 익명함수라서 리턴할 수 없다는 문법오류가 발생한다는 점이다.
한편, 함수에서 다른 함수의 실행문 자체를 리턴할 수도 있다. a 함수를 선언하고 b함수의 리턴값으로 a()를 돌려주도록 한 후 b함수를 실행하면, b 함수의 실행부 첫줄부터 차례대로 실행된 후 리턴값인 a() 의 실행결과가 차례대로 실행된다. 이미 한번 실행한 a의 결과값을 재현시켜주는 개념인 듯하다. (확실하지 않습니다 고차함수 혹은 일급함수의 실행방식에 대한 이해가 아직 완전히 되지 않아서요.)
https://gist.github.com/yeonwooz/7258118af26d9815d2f35249ca19801c
useEffect 내부로 전달된 count의 초기 상태값인 0 이 setInterval 에 있는 클로저에 전달된다. setInterval 은 1초마다 한번씩 실행되지만 계속해서 count를 0으로 알고 있어서 결과적으로 count 는 1에서 더이상 증가하지 않는다.
이에 대해 여러가지 해결방법이 있다고 한다. 그중 하나는, useRef 의 current 에 상태업데이트함수를 콜백함수로 직접 할당하고 이것을 useEffect 에서 생성한 setInterval 의 클로저에서 호출하도록 해서, setInterval 의 클로저가 1초에 한번 실행될 때마다 ref.current 의 콜백이 호출되도록 하는 방법이다. 그러면 콜백함수는 setInterval 의 클로저와 독립적으로 상태업데이트를 수행할 수 있다.
https://gist.github.com/yeonwooz/46717c9c18360d0f30d54a22b172f683
Photo by Pavan Trikutam on Unsplash
TMI
아직도 리액트 훅을 사용할 때 이렇게 버벅인다 ... 안돼. 어서.. 더 빨리 공부해 ...
TMI2
패러렐(https://www.parallels.com/kr/)이라는 것을 알게 되었다. 맥에서 윈도우를 실행시킬 수 있다.
이걸 접하게 된 배경을 구구절절 설명하자면, 라이브러리 내부로직을 살펴보다가 자바가 나와서 어쩌다보니 자바가 기본적으로 어떻게 동작하는지 궁금해졌고, 자바를 조금만 공부해보려고 시도해보다가 책을 샀는데 윈도우 환경에서 실습할 수 있는 책이었고, 맥밖에 없다고 한탄하자 지인이 패러렐을 소개해준 것이다.
하지만 자바코드는 맥에서도 작성할 수 있고 또 자바를 당장 그렇게 많이 공부할 것 같지도 않고.. 해서 혹여 패러렐을 쓴다고 하더라도 IDE를 쓰기보단 한글(HWP) 문서를 작성할 일이 있을 때 가장 유용할 것 같다는 생각이 들었다.
참고 자료
https://velog.io/@jakeseo_me/번역-리액트-훅스-컴포넌트에서-setInterval-사용-시의-문제점
https://ko.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
https://ko.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often