https://reactjs.org/docs/hooks-effect.html
공식문서 설명이 한글로 거지같아서 영어로 읽고 한번 정리하는 내용임
- useEffect는 class Component의 componentDidMount, componentDidUpdate, componentWillUnmount 에 대응되는 개념이다.
- 많은 케이스에서 componentDidMount와 componentDidUpdate에 똑같은 작업을 동시에 수행하는 경우가 있었다. 이에 페이스북은 useEffect에 두 기능을 한번에 묶어버렸다. 물론 따로 if문으로 두 작업을 분기하는 방법이 있음
- 특정 변수에만 effect를 사용하고 싶으면 deps를 이용한다
- deps에 빈 배열을 넣으면 DidMount만 수행 할 수 있다
- Unmount의 경우 useEffect에서 함수를 리턴하는 형식으로 구현된다.
- cleanup의 개념은 unmount와 조금 다르다
- cleanup을 사용하니 Mount->Update->Unmount 라이프 사이클 대비 코드도 깔끔해지고 버그도 줄어든다
Effects Without Cleanup
보통 리액트가 DOM을 업데이트 한 후 추가적인 작업이 필요한 경우가 있다. 가령 네트워크 리퀘스트라던지, DOM의 직접적인 수정, 또는 로그를 찍는게 대표적인 effects의 예시일 것이다. 그리고 이 예시들은 cleanup을 요구하지 않는다.
다시 말하면 위 effects 케이스들은 우리가 수행한뒤 그냥 잊어버리고 살아도 아무 문제 없는 예시들이다.
컴포넌트 클래스에서 effects예시
컴포넌트 클래스에서 cleanup이 필요없는 effects는 componentDidMount와 componentDidUpdate를 사용한다
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
위 코드에서 문제점은 동일한 작업을 두 함수에서 사용하고 있다는 것이다.
앞선 요약에서 언급한 것 처럼 많은 케이스에서 mount와 update에 동일한 작업을 수행하는 경우가 많다.
똑같은 작업을 두 함수에 나눠서 실행하면 코드의 낭비 뿐만 아니라 관리에도 어려움이 있을 것이다.
실제로 페이스북에서도 말 하길, mount와 update에 동일한 코드 작업을 하는 경우가 많았다고 한다.
Effect Hook을 사용한 예제
다음은 함수컴포넌트에서 useEffect를 사용한 예제이다
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위 예제에서는 useEffect 하나의 함수로 DidMount와 DidUpdate가 묶인것을 볼 수 있다.
즉 위 예제에서는 컴포넌트가 업데이트 될 때 마다 useEffect안에 있는 함수가 호출된다.
여기서 아래와 같은 궁금증이 생길 수 있다
나는 컴포넌트가 Mount만 될 때만 작업을 수행하고 싶어요!
물론 방벙이 있다! 사실 useEffect는 아래와 같은 형태의 함수이다
useEffect(function, [deps : effect의 대상이될 변수들 모음]);
여기서 function은 effect시 실행될 함수이도 중요한건 deps이다!
deps
이놈들은 한마디로 effect의 대상이 될 변수들의 모음이다. 앞선 예제를 조금 변형하여 문제 상황을 만들어보자
import React, { useState, useEffect } from 'react';
function Example() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
useEffect(() => {
document.title = `You clicked ${count1} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount1(count1 + 1)}>
Click me1
</button>
<button onClick={() => setCount2(count2 + 1)}>
Click me2
</button>
</div>
);
}
앞선예제에서 count를 count1과 count2 두개로 늘렸다. 위 상황에서 나는 count1이 변할때만 로그를 띄우고 싶다. count2를 증가시켜도 useEffect가 호출되게 된다.
나는 count1이 변할때만 로그를 호출하고 싶은데 말이다. 이때 해결법은 아래처럼 deps에 count1를 지정해주면 된다!
useEffect(() => {
document.title = `You clicked ${count1} times`;
}, [count1]);
그럼 count1이 변할 때만 로그가 출력된다.
import React, { useState, useEffect } from 'react';
function Example() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
useEffect(() => {
document.title = `You clicked ${count1} times`;
}, [count1]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount1(count1 + 1)}>
Click me1
</button>
<button onClick={() => setCount2(count2 + 1)}>
Click me2
</button>
</div>
);
}
자 그럼 앞선 질문, componentDidMount일 때 만 작업을 수행하려면 어떻게 하면 될 까?
정답은 deps에 빈배열을 넣어주면 된다!
useEffect(()=>{
console.log("mount!");
}, []);
Effects with Cleanup
앞서 우리는 DidMount와 DidUpdate 하는법을 배웠다. 그럼 Unmout는 어떻게 하는가?
정답은 useEffect안에 함수를 리턴해주면 리턴해주는 함수가 Unmount를 수행한다!
허나 한가지 기억해야 할 것이 있다. Cleanup은 unmount와 조금 다른개념이다
클래스 컴포넌트에서의 예제
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
함수 컴포넌트에서의 예제, useEffect 사용
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);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Unmount와 Cleanup의 다른점
Unmount는 컴포넌트가 Unmount될 때 불린다. cleanup도 마찬가지로 Unmount시점에서 호출된다.
하지만 cleanup은 추가로 컴포넌트가 매번 리렌더링 될 때 마다, 다음 effect가 발생하기전에 호출된다.
어쨋든 중요한건 리렌더링 될 때 마다 매번 호출된다는 것
왜 useEffect를 이렇게 만들었을 까?
아래 문제상황을 확인하자.
앞선 예제에서 Friend를 subscribe하고 unsubscribe 하는 걸 보았을 것이다. 이는 현재 접속하고 있는 친구가 온라인상에 있는지 없는지를 확인하는 api이다
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
헌데 중간에 friend의 prop이 바뀌었다면? 친구의 정보는 바뀌었는데 나는 이전에 데이터로 잘못된 친구의 정보를 참조하고 있게된다. 그리고 이는 메모리릭과 언마운트 시점에서 잘못된 정보로 unsubscribe하여 crash를 유발할 수 있다.
이를 해결할 방법은 DidUpdate를 활용하여 친구의 정보를 업데이트 하는 것 이다
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate(prevProps) {
// Unsubscribe from the previous friend.id
ChatAPI.unsubscribeFromFriendStatus(
prevProps.friend.id,
this.handleStatusChange
);
// Subscribe to the next friend.id
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
해결됐네? 그럼 도대체 뭐가 문제인가?
페이스북에서 말하길, 개발자들이 자주하는 실수로 Mount와 Unmout로직은 만들었는데 Update에서 적절한 예외처리 하는걸 까먹는 경우가 많아 많은 버그들을 만들어낸다고 한다
자 다음은 Effect Hook을 이용하여 위 예제를 구현해보자
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
깔끔하기 그지 없다.
즉 요약하면 다음과 같다
Mount -> Update -> Unmount의 라이프 사이클의 개념을
effect -> cleanup -> effect -> cleanup 이런식으로 바꾸었더니
코드도 깔끔해지고 버그도 잘 안생기게 되었다!
라고 말하는 것이다
'React' 카테고리의 다른 글
useHref() may be used only in the context of a <Router> component. (0) | 2022.09.19 |
---|---|
Objects are not valid as a react child 해결법 (0) | 2022.09.19 |
React Router v6.4 튜토리얼 배우기 (1) | 2022.09.16 |
Fragment 사용법 (0) | 2022.09.15 |
리액트 커스텀 환경변수 사용하기 (0) | 2022.09.14 |
댓글