useEffect
useEffect 기본 문법
useEffect(setup, dependencies?)
- useEffect 렌더링 후 mount 시점에 실행
- 클래스형 컴포넌트 : componentDidMount 시점에 비동기로 실행
- 함수형 컴포넌트 : return JSX(render) 다음 실행
- cleanup 코드는 componentWillUnMount 시점에 실행
- re-render 후에 React는 cleanup 함수를 이전 값으로 실행한 다음, 새 값으로 설정 함수를 실행함
- 컴포넌트가 DOM에서 제거된 후, React는 정리 함수를 실행
- 의존성 배열 전달과 리렌더링의 연관 관계
- 종속성 배열 전달 : 초기 렌더링에 실행, 종속성이 변경되어 다시 실행된 경우에도 실행
- 빈 종속성 배열 전달 : 효과가 반응형 값을 전혀 사용하지 않는 경우 초기 렌더링에만 실행(1회만)
- 종속성 배열을 전혀 전달하지 않을 경우 : 렌더링 및 리렌더링 할 때마다 실행
- 의존 관계 지정 시 primitive는 값, reference는 주소임
useEffect와 <StrictMode>
- useEffect의 경우 의존성 배열이 비워([])져있을 때 두 번 호출됨
- Mount - UnMount - Mount
- 2회 마운트 / 호출 가능한 부분의 안전한 코딩을 위해!
- useState, useEffect, useReduce의 callback을 2회 실행
- Strict Mode를 꺼두면 잘못된 코드를 작성할 가능성이 높기 때문
- clean-up 함수 유실, 중복 데이터 처리 등
⇒ clean-up 해줄 것이 있는지 항상 염두하고 코드 짜기
useEffect와 비동기(Promise)
- Promise는 실행될 때 callback 함수를 background로 제어를 넘김
- re-render 등으로 UnMount시에 Promise는 background에 존재하므로 clean-up되지 않음
⇒ clean-up 코드에서 **Promise를 중지(abort)**해줘야 리렌더링되지 않음 (정석)
(단, request server는 2회)
controller = new AbortController(); // non-signal: 3회 render (TQ에 2번 들어감)
const { signal } = controller; // with-signal: 2회 render (처음 cb가 abort → TQ 1번)
fetch(url, { signal }); // ~~~ controller.abort() at cleanup function!!
BAD CODE
useEffect(() => {
const url = '/data/sample.json';
fetch(url)
.then((res) => res.json())
.then((data) => {
setSession(data);
})
.catch(console.error);
}, []);
⇒ SWR, React-Query 고려
GOOD CODE
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
fetch(url, { signal })
.then((res) => res.json())
.then((data) => {
setSession(data);
})
.catch(console.error);
return () => controller.abort();
}, []);
useRef와 useEffect
- 렌더링 후 DOM 제어 시, useRef는 useEffect를 필요로 함
- componentDidMount 되기 전에 DOM 참조 안됨
- element가 마운트되기 전에는 ref는 undefined | null
- 자식 컴포넌트의 DOM을 useRef로 참조 시 forwardRef + useEffect 필수!
const Login = () => {
…
useEffect(() => {
console.log('Login, please!');
userIdRef.urrent?.focus();
return () => {
console.log('로그인 되셨어요');
};
}, []); // 단 1번의 효과!
}
{session.loginUser ? <Profile ref={logoutBtnRef} /> : <Login />}
// Profile.tsx (18.3+에서는 forwardRef 불필요!)
const Profile = forwardRef((props, ref) => {
…
<button ref={ref}>Logout</button>