리액트 라이브러리의 주요 작업은 단언컨데, 사용자의 이벤트에 상호작용하여 컴포넌트를 그에 맞게 렌더링 혹은 리 렌더링 하는 것 입니다. 하지만 앱을 만드는데 있어 프론트엔드가 해야할 부분이 그것만 있는 것은 아니죠. 로컬 저장소 데이터를 저장하거나, 잠정적 에러를 처리하거나, http request
를 보내는 것도 필요합니다.
side effect
는 컴포넌트를 다루는 주요 업무외에 어플리케이션에 필요한 작업을 말합니다. 리액트 자체가 필요한 업무나 관련된 업무는 아니기에, 리액트와는 별개로 독립적으로 운영되어야 하는 부분입니다.
만약 리액트 컴포넌트 안에서 API를 요청하는 함수를 만들었다고 해봅시다. 리액트는 상태에 따라, 해당 컴포넌트를 리렌더링 할텐데, 계속 요청을 반복해서 보내게 되지 않을까요??
useEffect()
는 useState()
와 마찬가지로 별도의 리액트 상태관리 훅 입니다. useState()
가 state
변경 시마다 자동으로 컴포넌트를 리렌더링 하였다면, useEffect()
는 특정 종속성을 가질 수 있고, 그 해당 종속성이 변화할 때만 useEffect()
를 실행할 수 있습니다.
useEffect()
는 컴포넌트 처음 렌더링 할 때를 제외하고, 특정 종속성에만 호출되는 것을 앞서 설명했습니다. 예를 들어 종속성을 인자로 넣지 않은 체, state
를 변경하는 작업을 실행했다면, 해당 컴포넌트는 무한 루프에 빠지게 됩니다.
- 브라우저에 처음 렌더링 될 때,
useEffect()
가 호출된다.useEffect()
의state
변경으로 컴포넌트가 다시 렌더링 된다.- 다시 리렌더링 되면서,
useEffect()
를 또 호출한다.- 2 -> 3 을 계속 반복한다.
const Login = (props) => {
const [enteredEmail, setEnteredEmail] = useState("");
const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
useEffect(() => {
const indentifier = setTimeout(() => {
console.log("checking form validity");
setFormIsValid(enteredEmail.includes("@") && enteredPassword.trim().length > 6);
}, 500);
// cleanup function
return () => {
console.log("cleaning up");
clearTimeout(indentifier);
};
});
...
...
}
해결방법은 종속성 인자를 빈 배열 형태로 넣는 것 입니다. 이렇게 하면 해당 useEffect()
는 처음 렌더링 할 때 외에는 절대 호출되지 않는 함수가 됩니다.
const Login = (props) => {
const [enteredEmail, setEnteredEmail] = useState("");
const [emailIsValid, setEmailIsValid] = useState();
const [enteredPassword, setEnteredPassword] = useState("");
const [passwordIsValid, setPasswordIsValid] = useState();
const [formIsValid, setFormIsValid] = useState(false);
useEffect(() => {
const indentifier = setTimeout(() => {
console.log("checking form validity");
setFormIsValid(enteredEmail.includes("@") && enteredPassword.trim().length > 6);
}, 500);
// cleanup function
return () => {
console.log("cleaning up");
clearTimeout(indentifier);
};
}, []);
...
...
}
useEffect()
의 종속성으로는 값이 바뀌는 거라면 어떤 것이든 추가하여 넣는 것이 가능합니다. 즉, 모든 상태 변수, 함수도 가능하다는 것입니다. 따라서 사용할 때는 적절한 종속성을 골라 넣는 것이 매우 중요합니다. 지금부터 나열되는 것들은 수업에서 강조하거나, 리액트에서 권장하거나, 개발자들 사이에서 통용되는 관례적인 부분입니다. 다소 편향된 얘기 일 수 있습니다.
useState
의 상태 업데이트 함수 : 리액트는 state
를 통해 해당 데이터가 직접적으로 변경되지 않도록 보장합니다. 종속성으로 추가할 필요가 없습니다.useEffect()
가 호출되지 않습니다.)간단히 말하자면, 리액트 라이프사이클의 영향을 받으며, 상태가 변화 할 때마다 컴포넌트가 다시 렌더링 되게 만드는 것들을 종속성으로 추가하는 것이 좋습니다.
useEffect()
는 해당 종속성이 변화할 때 마다, 해당 이벤트가 호출됩니다. 만약 이벤트 가운데, 상태가 바뀔 때 마다 나타날 필요 없이, 사용자 요청 마지막에만 한번만 적용하여 바꾸면 훨씬 효율적이지 않을까요??? 어떻게 하면 그게 가능할까요??
정답은 해당 useEffect()
의 이벤트가 적용되기 전, 해당 이벤트를 return
시켜 버리면 됩니다.
이러한 작업을 디바운싱이라고 합니다!
// 사용자의 입력 후, 500ms 이후, 유효성 검사를 적용함
// cleanup 함수를 통해, 이벤트 적용 전, 이벤트 무효화
// 결과적으로 여려번 요청이 들어오는 경우, 마지막 한번의 이벤트만 적용되어 호출되게 됨
useEffect(() => {
const indentifier = setTimeout(() => {
console.log("checking form validity");
setFormIsValid(enteredEmail.includes("@") && enteredPassword.trim().length > 6);
}, 500);
// cleanup function
return () => {
clearTimeout(indentifier);
};
}, [enteredEmail, enteredPassword]);