
인트로
useState는 어제로 맛 잘 봤고 이제 useEffect에 대해 알아보려고 합니다.
아직 모든 강의를 다 보지는 못했지만 벌써부터 새로운 지식들이 추가되고 좋네요
useEffect
Effect Hook을 사용하면 함수 컴포넌트에서 side effect를 수행할 수 있다고 합니다.
근데 side effect가 정확히 뭐죠..?
React 컴포넌트가 화면에 렌더링 된 이후에 비동기로 처리되어야 하는 부수적인 효과를 Side Effect라고 합니다.
리액트에서는 데이터 가져오기, 구독 설정하기, 수동으로 React 컴포넌트의 DOM을 수정하는 것 등이 side effect에 해당된다고 하네요. (익숙하지 않을 수 있다고 공감까지,.. 감동입니다.)
React 컴포넌트에는 일반적으로 두 종류의 side effects가 있습니다.
정리(Clean-up)를 이용하지 않는 Effects
React가 DOM을 업데이트한 뒤 추가로 코드를 실행해야 하는 경우가 있습니다.
network request, DOM 수동조작 등은 정리가 필요 없는 경우입니다. 실행 이후에 신경 쓸 것이 없기 때문이죠.
정리가 필요없는 경우 어떤 것도 반환할 필요 없습니다.
정리(Clean-up)를 이용하는 Effects
React는 컴포넌트가 마운트 해제되는 때에 정리를 실행합니다.
effect에 정리가 필요한 경우에는 함수를 반환하면 됩니다.
의존성 배열
모든 렌더링 이후에 effect를 정리하거나 적용하면 성능저하를 발생시킬 수 있습니다.
이를 방지하려면 useEffect의 두 번째 인수로 배열을 넘기면 되는데요.
배열 안에 특정 값을 넣을 경우 해당 값이 변경될 때만 effect를 재실행합니다.
즉 특정 값들이 변경되지 않는다면 effect를 건너뛰는 것이죠.
effect를 딱 한번만 실행하고 싶다면 빈 배열을 넘기면 됩니다.
더 깊은 내용은 useEffect를 계속 공부하면서 추가하도록 하겠습니다.
지금은 코드를 치면서 동작을 확인하고 익숙해지는 게 더 중요할 것 같습니다.
간단한 기초 예제부터 살펴보죠.
useEffect Basic
return (
<>
<h2>Hi</h2>
<button onClick={() => { setNumber(number + 1);}}>
{number}
</button>
<button onClick={() => {setANumber(aNumber + 1);}}>
{aNumber}
</button>
</>
);
두 개의 버튼을 가집니다.
첫 번째 버튼을 클릭하면 number 변수의 값이 갱신되고 두 번째 버튼을 클릭하면 aNumber의 값이 갱신됩니다.
useEffect()
'Hello'를 콘솔에 출력하는 함수인 sayHello가 실행됩니다.

의존성배열에 number가 들어있기 때문에 number 값이 갱신될 때마다 sayHello 함수가 실행하게 됩니다.
const [number, setNumber] = useState(0);
const [aNumber, setANumber] = useState(0);
const sayHello = () => console.log("Hello");
useEffect(() => {
sayHello();
}, [number]);

두 번째 버튼 클릭 시에는 실행이 안 되는 모습입니다.
의존성 배열에 빈 배열을 넣게 된다면 어떻게 될까요? 최초 마운트 이후에는 아무 일도 일어나지 않습니다.

두 번째 인자에 아무것도 주지 않게 되면 미친 듯이 인사를 합니다.

의존성배열에 따라 effect 실행이 결정이 되기 때문에 의존성배열에 넣을 값을 잘 생각해야 합니다.
이제 useEffect를 이용해 만든 Custom Hook을 살펴보죠.
Custom Hook
useTitle
문서의 제목을 update해주는 훅입니다. 여기서 title은 브라우저 타이틀입니다.
const useTitle = (initialTitle) => {
const [title, setTitle] = useState(initialTitle);
const updateTitle = () => {
const htmlTitle = document.querySelector("title");
htmlTitle.innerText = title;
};
useEffect(updateTitle, [title]);
return setTitle;
};
useTitle
인자로 초기 title 값을 받습니다.
title 관련 state와 title을 갱신할 수 있는 함수를 가지고 있습니다.
setTitle 함수를 반환합니다.
updateTitle & useEffect
title값이 변경될 경우 effect를 실행합니다. 즉 updateTitle 함수가 실행됩니다.
updateTitle 함수는 title을 업데이트합니다. htmlTitle는 title 요소 노드입니다. title 텍스트 콘텐츠를 title로 바꿔줍니다.
const titleUpdater = useTitle("Loading...");
setTimeout(() => titleUpdater("Main"), 5000);
초기값으로는 'Loading...'을 주었습니다. state 변수인 title의 값에는 Loading... 이 들어가게 되겠죠.
useTitle은 setTitle이라는 함수를 반환합니다. 따라서 setTitle함수는 titleUpdater 변수에 저장됩니다.
setTimeout()을 사용해 5초 후 titleUpdater()로 호출합니다. 결과적으로 5초 후 title이 "Loading.."에서 "Main"으로 갱신되고 title이 변경되었으니 useEffect가 updateTitle을 호출하겠죠.

useClick
이 커스텀 훅에 대해 알아보기 전에 useRef() 훅부터 살펴봐야 합니다.
const element =useRef(initialValue);
useRef는 .current 프로퍼티로 전달된 인자(initialValue)로 초기화된 변경 가능한 ref 객체를 반환합니다.
쉽게 말하자면 getElementById, querySelector 같은 역할을 한다고 보면 됩니다.
React에서도 가끔 DOM을 직접 선택해야 하는 상황이 발생합니다.
이때 리액트에서는 ref를 사용하는데요. 함수형 컴포넌트에서 ref를 사용할 때 useRef Hook을 사용합니다.
참고로 useRef는 내용이 변경될 때 그것을 알려주지 않습니다. .current 프로퍼티를 변형한다고 리렌더링을 발생시키지 않습니다.
const input = useRef();
setTimeout(() => {
input.current.focus();
console.log(1);
}, 3000);
useRef
이제 input은 const input과 같습니다.
3초 뒤에 input 텍스트박스에 focus를 줍니다.
<input placeholder="la" ref={input} />

이제 다시 useClick을 살펴보겠습니다.
<h1 ref={title}>Hi</h1>
const sayHello = () => {
console.log("hello");
};
const title = useClick(sayHello);
useClick
인자로 콜백함수를 받습니다.
두 번째 라인으로 인해 title을 이용하여 h1을 조작할 수 있습니다.
useEffect
이 예제에서 element.current 는 h1 노드를 가리키게 됩니다.
마운트 시 DOM노드가 있을 경우 해당 노드에 click 이벤트를 추가해 줍니다. click 시 콜백함수가 실행됩니다.
함수를 반환하기 때문에 언마운트 시 해당 노드의 이벤트를 제거합니다.
의존성배열에 빈 배열을 주었기 때문에 마운트하거나 언마운트 시에 딱 한 번만 실행됩니다.
const useClick = (onClick) => {
const element = useRef();
useEffect(() => {
if (element.current) {
element.current.addEventListener("click", onClick);
}
return () => {
if (element.current) {
element.current.removeEventListener("click", onClick);
}
};
}, []);
return element;
};
이 예제에서는 h1 클릭 시 sayHello 가 실행되겠죠. 이제 확인해 봅시다.

useBeforeLeave
마우스의 y 좌표를 확인하고 특정 좌표 밖으로 나갈 경우 원하는 동작을 하는 커스텀 훅입니다.
전체 코드는 다음과 같습니다. 천천히 살펴보죠.
const useBeforeLeave = (onBefore) => {
const handle = (event) => {
const { clientY } = event;
if (clientY <= 0) onBefore();
};
useEffect(() => {
document.addEventListener("mouseleave", handle);
return () => {
document.removeEventListener("mouseleave", handle);
};
}, []);
};
function App() {
const begForLife = () => console.log("가지마");
useBeforeLeave(begForLife);
return (
<>
<h1>hello</h1>
</>
);
}
export default App;
useBeforeLeave
onBefore이라는 콜백함수를 받습니다.
handle
event에서 clientY 값을 가지고 옵니다. clientY는 이벤트가 발생한 뷰포트 내에 수직 좌표를 가지고 있습니다.
뷰포트의 상단 가장자리의 clientY 값이 0입니다.
이 clientY <= 0 일 때 즉 뷰포트를 상단을 벗어났을 때 콜백함수를 실행합니다.
useEffect
'mouseleave' 말 그대로 마우스 커서가 요소 밖으로 나갔을 때 발생하는 이벤트입니다. document 즉 페이지에 해당 이벤트를 추가해 줬습니다. 이제 현재 페이지를 벗어나면 handle 함수가 호출되고 clienY 값을 확인해 상단으로 마우스가 나간 경우 콜백함수를 실행하겠죠.
begForLife
대충 가지 말라고 붙잡는 함수입니다.

useScroll
특정 값만큼 스크롤할 경우 요소의 스타일을 변경하는 커스텀 훅입니다.
const useScroll = () => {
const [state, setState] = useState({
x: 0,
y: 0,
});
const onScroll = () => {
setState({ y: window.scrollY, x: window.scrollX });
};
useEffect(() => {
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
return state;
};
function App() {
const { y } = useScroll();
return (
<div style={{ height: "1000vh" }}>
<h2 style={{ position: "fixed", color: y > 1000 ? "red" : "blue" }}>
hi
</h2>
</div>
);
}
useScroll
scroll 관련 state가 정의되어 있습니다. 초기값은 x, y 모두 0입니다. 사실 이 코드에서 x는 필요 없습니다.
state를 반환합니다.
onScroll
scroll 이벤트가 발생할 때 호출되는 함수입니다.
setState 함수를 통해 scroll의 상태가 담긴 state 값을 갱신합니다.
window.ScrollY는 문서가 수직으로 얼마나 스크롤됐는지 픽셀 단위로 반환해 줍니다. x는 수평으로 얼마나 스크롤됐는지겠죠.
useEffect
여태까지 해온 것처럼 마찬가지로 최초 마운트에 이벤트를 추가하고 언마운트 시 이벤트를 제거해 줍니다.
window에 이벤트 리스너를 붙였는데요. scroll은 관습적으로 window에 붙인다고 합니다.
참고로 window는 전체 페이지를 의미합니다. document도 전체 페이지인데 window가 더 큰 개념이라고 보면 될 것 같습니다.
실제로 document.addEventListener('scroll', onScroll); 로 바꿔도 동작에 문제는 없습니다.
App
반환한 마우스 값을 받습니다. y만 필요해서 y만 가져왔습니다.
scrollY 값이 1000보다 클 경우 글씨 색이 blue에서 red로 바뀝니다.
참고로 저는 useRef 사용해서 콘텐츠 내용도 바꿔봤습니다. 해당 코드는 더 보기를 참고해 주세요.
const useScroll = () => {
const $h2 = useRef();
const [state, setState] = useState({
x: 0,
y: 0,
});
const onScroll = () => {
setState({ y: window.scrollY, x: window.scrollX });
if ($h2.current)
$h2.current.innerText = window.scrollY > 1000 ? "red" : "blue";
};
useEffect(() => {
document.addEventListener("scroll", onScroll);
return () => document.removeEventListener("scroll", onScroll);
}, []);
return { $h2, state };
};
function App() {
const {
$h2,
state: { y },
} = useScroll();
return (
<div style={{ height: "1000vh" }}>
<h2
ref={$h2}
style={{ position: "fixed", color: y > 1000 ? "red" : "blue" }}
>
hi
</h2>
</div>
);
}

useNetwork
navigator가 online이거나 offline 일 때 원하는 동작을 하는 커스텀 훅입니다.
const useNetwork = (onChange) => {
const [status, setStatus] = useState(navigator.onLine);
const handleChange = () => {
if (typeof onChange === "function") onChange(navigator.onLine);
setStatus(navigator.onLine);
};
useEffect(() => {
window.addEventListener("online", handleChange);
window.addEventListener("offline", handleChange);
return () => {
window.removeEventListener("online", handleChange);
window.removeEventListener("offline", handleChange);
};
}, []);
return status;
};
function App() {
const handleNetworkChange = (online) =>
console.log(online ? "you are online" : "you are offline");
const onLine = useNetwork(handleNetworkChange);
return (
<>
<h2>{onLine ? "Online" : "Offline"}</h2>
</>
);
}
navigator.onLine은 브라우저의 online 상태를 boolean 값으로 반환해 줍니다. online 일 경우 true, offline 일 경우 false
useNetwork
콜백함수를 파라미터로 받고 status를 반환합니다.
여기서 status는 navigator의 online 상태가 담겨있습니다.
handleChange
파라미터가 함수라면 콜백함수를 실행합니다. 이때 콜백함수에 navigator의 online 상태를 arguments로 보냅니다.
status 값을 현재 online 상태로 갱신합니다.
useEffect
online과 offline 이벤트를 추가해 줍니다. online 이벤트의 경우 브라우저가 네트워크에 액세스 한 후 Navigator.onLine 값이 true로 전환되면 online 이벤트가 실행됩니다. offline은 반대입니다.
useNetwork로부터 반환받은 값에 따라 콘솔창과 h2요소의 콘텐츠가 달라집니다.

이 포스팅은 여기서 마치려고 합니다.
useEffect를 사용한 커스텀 훅이 하나가 더 있는데 그 친구는 좀 복잡해서 따로 다뤄보려고 합니다.
처음부터 파일을 정리하면서 했어야 했는데 초반에 그냥 주석처리하면서 한 파일 내에서 하다 보니 앞에 만든 커스텀 훅들은 전체 코드가 아니라 나눠서 보여 가독성이 떨어지네요.. 아쉽.. 언젠가..다시...정리..할지..
노마드코더 실전형 리액트 훅 강의 내용을 참고하였습니다.
'내가 해냄 > React' 카테고리의 다른 글
리액트에서 SCSS 사용하기 내가 해냄 (0) | 2023.03.26 |
---|---|
useAxios 내가 해냄 (2) | 2023.03.23 |
[유용한 기능]useNotification, useFullscreen 내가 해냄 (1) | 2023.03.23 |
[유용한 기능]useConfirm, usePreventLeave 내가 해냄 (0) | 2023.03.22 |
useState 내가 해냄 (0) | 2023.03.21 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!