Effect란?(Side Effect)
리액트의 주요 임무는 UI를 렌더링하고, 사용자 입력에 반응하는 것이다.
즉, JSX 코드를 평가하고 렌더링한다. 그리고 state와 props를 관리하고, 사용자 입력과 이벤트에 반응하고, state와 props가 변하면 컴포넌트를 재평가한다.
effect(side effect)는 애플리케이션에서 일어나는 위의 일들과 다른 모든 것을 의미한다. 일반적으로 함수가 실행되면서 함수 외부 상태가 변경되는 것을 의미한다.
예를 들어 브라우저 스토리지에 데이터를 저장하거나, 백엔드 서버로 Http 요청을 보내는 것이다.
이것들은 화면에 무언가를 가져오는 것과는 직접적인 관련이 없다. 즉, 이것들은 일반적인 컴포넌트 평가의 밖에서 일어나야 하는 일이다.
예를 들어 로그인 정보를 로컬 스토리지에 저장한 경우 이 데이터를 가져오는 것을 컴포넌트 함수 안에서 직접한다면 무한루프를 만든다.
function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const userLoggedIn = localStorage.getItem('isLoggedIn');
if (userLoggedIn === '1') {
setIsLoggedIn(true);
}
const loginHandler = (email, password) => {
localStorage.setItem('isLoggedIn', '1');
setIsLoggedIn(true);
};
...
로그인 정보가 저장되어 있으면 setstate 함수를 호출해 true로 설정하는데, 이 상태 업데이트 함수를 호출할 때마다 컴포넌트 함수는 다시 실행되고, 이 작업은 반복되면서 무한 루프가 된다.
따라서 사이드이펙트를 처리하는 useEffect 훅이 필요하다.
useEffect
useEffect(() => { ... }, [ dependencies ]);
- 첫 번째 인수: 모든 컴포넌트 평가 후에 실행되어야 하는 함수 (지정된 의존성이 변경된 경우)
- 두 번째 인수: 의존성으로 구성된 배열
지정된 의존성이 변경될 때마다 첫 번째 함수가 실행된다. 이 함수에 사이드이펙트 코드를 넣으면 된다.
🌟 사이드이펙트 함수는 모든 컴포넌트 평가 후에 실행된다!
useEffect(() => {
const userLoggedIn = localStorage.getItem('isLoggedIn');
if (userLoggedIn === '1') {
setIsLoggedIn(true);
}
}, []);
의존성 배열에 아무것도 넣지 않는다면 앱이 시작되고 한 번만 실행된다.
useEffect(() => {
setFormIsValid(
enteredEmail.includes('@') && enteredPassword.trim().length > 6
);
}, [enteredEmail, enteredPassword]);
이메일과 비밀번호 입력 폼의 유효성을 검사하는 로직이다. 사용자가 입력한 enteredEmail, enteredPassword가 변경될 때마다 첫 번째 인수에 넣은 함수가 실행된다.
의존성 배열 (dependency array)
여기서, 의존성 배열에 어떤 것을 넣을건지에 대해 이펙트 함수에서 참조(사용)하는 것을 의존성에 추가하면 된다.
예를들어 컴포넌트의 상태나 props(컴포넌트가 재평가될 때 변경될 수 있는 것들), 컴포넌트에서 정의된 변수나 함수가 될 수 있다.
여기선 setFormIsValid, enteredEmail, enteredPassword가 있다.
setFormIsValid는 생략할 수 있는데 이런 상태 업데이트 함수는 리액트에 의해 절대 변경되지 않도록 보장되기 때문이다.
Cleanup(클린업) 함수
위에서 봤던 코드처럼 키가 입력될 때마다 setFormIsValid 함수를 실행한다고 생각해보자.
만약 여기서 다른 컴포넌트 실행이 발생해 리액트가 DOM에서 무엇을 변경해야 하는지 여부를 확인해야 한다면 키를 입력할 때마다 저 함수를 실행하지는 않을 것이다.
다른 예를 들면 백엔드로 http 요청을 보낼 때 키가 입력될 때마다 요청을 보내는 것은 불필요한 네트워크 트래픽을 만들 것이다.
따라서 키 입력 후 일정 시간 동안 일시중지(여기선 500ms) 되는 것을 기다리는 것을 생각해보자. 사용자가 타이핑을 멈출 때를 기다리는 것이다.
원하는 작업은 사용자가 계속 타이핑하면 다른 모든 타이머는 계속 지워지고 마지막 타이머만 완료되는 것이다.
useEffect(() => {
const identifier = setTimeout(() => {
setFormIsValid(
enteredEmail.includes('@') && enteredPassword.trim().length > 6
);
}, 500);
return () => {
clearTimeout(identifier);
};
}, [enteredEmail, enteredPassword]);
useEffect에서 첫 번째 인수로 전달하는 함수에서 함수를 반환할 수 있다. 이 함수를 클린업 함수라고 하는데,
클린업 함수는 모든 새로운 사이드이펙트 함수가 실행되기 전에(첫 번째 사이드이펙트 함수가 실행되기 전은 제외), 컴포넌트가 언마운트 되기 전에 실행된다.
setTimeout이 타이머 id를 반환하므로 클린업 함수에서 clearTimeout 함수를 호출해 타이머 id를 전달한다. 이렇게 하면 클린업 함수가 실행될 때마다 클린업 함수가 실행되기 전에 설정된 타이머가 지워진다.
따라서 사용자가 입력을 빠르게 하면(500ms이내로) 이전에 설정된 타이머가 계속 지워지게 되어 setFormIsValid가 실행되지 않고, 500ms이상 일시정지했을 때에 setFormIsValid가 실행된다.
'React' 카테고리의 다른 글
[React] forwardRef (1) | 2023.09.15 |
---|---|
[React] useReducer (0) | 2023.09.14 |
[React] Portals, createPortal (0) | 2023.09.14 |
[React] JSX limitation, Fragment (0) | 2023.09.13 |
CSS Module (0) | 2023.09.11 |