Effect Hook
- side effect를 수행할 수 있다.
Side Effect란?
- 데이터 가져오기
- 구독(subscription) 설정하기
- 수동으로 React 컴포넌트 DOM 수정하기
- 결과를 예상할 수 없는 것
- 렌더링 과정에서 구현할 수 없는 작업
- 다른 컴포넌트에 영향을 줄 수 있는 작업
정리(Clean-up)를 이용하지 않는 Effects
- 네트워크 리퀘스트, DOM 수동 조작, 로깅 등은 정리가 필요 없는 경우들이다. 이러한 예들은 실행 이후 신경 쓸 것이 없기 때문
useEffect가 하는 일
- 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는 지를 말한다.
- useEffect 안의 함수는 DOM 업데이트를 수행한 이후에 불러낸다.
useEffect를 컴포넌트 안에서 불러내는 이유
- effect를 통해 state 변수 또는 prop에 접근할 수 있게 된다.
- 함수 범위 안에 존재하기 때문에 특별한 API 없이도 값을 얻을 수 있는 것
useEffect 렌더링 순서
- 기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행된다.
- 마운팅과 업데이트라는 방식으로 생각하는 대신 effect를 렌더링 이후에 발생하는 것으로 생각하는 것이 더 쉬울 것
- 컴포넌트를 렌더링할 때 React는 이용한 effect를 기억하였다가 DOM을 업데이트한 이후에 실행한다.
대부분의 effect는 동기적으로 실행될 필요가 없다.
- 흔하지는 않지만 동기적 실행이 필요한 경우에는 useEffect와 동일한 API를 사용하는 useLayoutEffect라는 별도의 Hook이 존재한다. (먼저 useEffect를 사용해 보고 문제가 있다면 그 다음으로 useLayoutEffect를 사용해 보기를 권한다.)
정리(clean-up)를 이용하는 Effects
- 외부 데이터에 구독(subscription)을 설정해야 하는 경우에는 메모리 누수 가 발생하지 않도록 정리하는 것은 매우 중요하다.
effect에 정리가 필요한 경우에는 함수를 반환한다.
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // 구독
// effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
effect에서 함수를 반환하는 이유?
- 이는 effect를 위한 추가적인 정리 메커니즘. 모든 effect는 정리를 위한 함수를 반환할 수 있다. 이 점이 구독의 추가와 제거를 위한 로직을 가까이 묶어둘 수 있게 한다.
React가 effect를 정리하는 시점은 언제?
- React는 컴포넌트가 마운트 해제되는 때에 정리를 실행한다.
- 위의 예시에서 보면 effect는 한 번이 아니라 렌더링이 실행되는 때마다 실행된다. React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect 또한 정리하는 이유는 메모리 누수나 충돌이 발생하는 것을 방지하기 위함이다.
Effect를 건너뛰어 성능 최적화하기
- 모든 렌더링 이후에 effect를 정리하거나 적용하는 것이 때때로 성능 저하를 발생시키는 경우도 있다.
- 클래스 컴포넌트의 경우에는
componentDidUpdate
에서prevProps
나prevState
와의 비교를 통해 이러한 문제를 해결할 수 있다.componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { document.title = `You clicked ${this.state.count} times`; } }
- useEffect에는 이러한 기능이 내장되어 있다. useEffect의 선택적 인수인 두 번째 인수로 배열을 넘기면 된다.
useEffect에서 의존성 배열을 사용할 때 주의해야 할 점
- 배열이 컴포넌트 범위 내에서 바뀌는 값들과 effect에 의해 사용되는 값들을 모두 포함해야 한다. 그렇지 않으면 현재 값이 아닌 이전의 렌더링 때의 값을 참고하게 된다.
Hook 규칙
- 최상위에서만 Hook을 호출해야 한다.
- 오직 React 함수 내에서 Hook을 호출해야 한다. (또는 Custom Hook)
자신만의 Hook 만들기
사용자 정의 Hook 추출하기
- 두 개의 자바스크립트 함수에서 같은 로직을 공유하고자 할 때는 또 다른 함수로 분리한다. 컴포넌트와 Hook 또한 함수이기 때문에 같은 방법을 사용할 수 있다.
- 사용자 정의 Hook은 이름이
use
로 시작하는 자바스크립트 함수이다. - 같은 Hook을 사용하는 두 개의 컴포넌트는 state를 공유하지 않는다. 사용자 정의 Hook은 상태 관련 로직을 재사용하는 매커니즘이지만 사용자 Hook을 사용할 때마다 그 안의 state와 effect는 완전히 독립적이다.
useEffect의 콜백이 개별 사용자 입력의 결과이거나 flushSync로 래핑된 업데이트의 결과인 경우
- 레이아웃 및 페인트 전에 동기적으로 실행된다.
useRef
- useRef hook은 DOM ref 만을 위한 것이 아니다. "ref" 객체는 현재 프로퍼티가 변경할 수 있고 어떤 값이든 보유할 수 있는 일반 컨테이너이다. 이는 class의 인스턴스 프로퍼티와 유사하다.
- useEffect 내부에서 쓸 수 있다.
- 지연 초기화를 수행하지 않는 한, 렌더링 중이 ref 설정을 피해라. 일반적으로 이벤트 처리와 effect에서 ref를 수정하는 것이 좋다.
고비용의 객체를 지연해서 생성하는 법
useMemo
를 사용하면 종속성이 동일한 경우 값비싼 계산을 메모할 수 있다. 그러나 힌트 역할을 할 뿐이며 계산이 다시 실행되지 않는다는 보장은 없다. 때로는 객체가 한 번만 생성되었는지 확인해야 한다.
1. 일반적인 사용 사례 : 초기 state를 만드는 데 비용이 많이 드는 경우
function Table(props) {
// ✅ createRows()는 한 번만 호출됩니다
const [rows, setRows] = useState(() => createRows(props.count));
// ...
}
- 무시된 초기 state를 다시 생성하지 않으려면
useState
에 함수 컴포넌트를 전달할 수 있다. - React는 첫 번째 렌더링 중에만 함수 컴포넌트를 호출한다.
useRef() 초깃값을 다시 작성하고 싶지 않을 때
예를 들어 명령형 class 인스턴스가 한 번만 생성되도록 하고 싶다면
function Image(props) { // ⚠️ IntersectionObserver는 모든 렌더링에서 생성됩니다 const ref = useRef(new IntersectionObserver(onIntersect)); // ... }
useRef
는useState
와 같은 특수 함수 컴포넌트 오버로드를 허용하지 않는다. 대신 느리게 생성하고 설정하는 자체 함수 컴포넌트를 작성할 수 있다.function Image(props) { const ref = useRef(null); // ✅ IntersectionObserver는 한 번 느리게 생성됩니다 function getObserver() { if (ref.current === null) { ref.current = new IntersectionObserver(onIntersect); } return ref.current; } // 필요할 때 getObserver()를 호출해주세요 // ... }
이렇게 하면 처음에 진정으로 필요할 때까지는 값비싼 개체를 만들지 않아도 된다.
'React' 카테고리의 다른 글
[React] 공식문서 주요 개념 10 ~ 12, HOOK 1 ~ 3정리 (0) | 2022.09.05 |
---|---|
[React] 공식문서 주요 개념 1 ~ 9 정리 (0) | 2022.09.04 |
[Router] React Router (0) | 2022.08.21 |
[Redux] Redux 기초 (0) | 2022.07.31 |
[Webpack] creat-react-app 없이 react 시작하기 (0) | 2022.06.14 |